diff --git a/Sources/SWBTaskConstruction/CMakeLists.txt b/Sources/SWBTaskConstruction/CMakeLists.txt index 3a32f552..a6dbac60 100644 --- a/Sources/SWBTaskConstruction/CMakeLists.txt +++ b/Sources/SWBTaskConstruction/CMakeLists.txt @@ -51,6 +51,7 @@ add_library(SWBTaskConstruction TaskProducers/StandardTaskProducer.swift TaskProducers/TaskProducer.swift TaskProducers/TaskProducerExtensionPoint.swift + TaskProducers/WorkspaceTaskProducers/CASConfigFileTaskProducer.swift TaskProducers/WorkspaceTaskProducers/BuildDependencyInfoTaskProducer.swift TaskProducers/WorkspaceTaskProducers/CreateBuildDirectoryTaskProducer.swift TaskProducers/WorkspaceTaskProducers/HeadermapVFSTaskProducer.swift diff --git a/Sources/SWBTaskConstruction/ProductPlanning/ProductPlanner.swift b/Sources/SWBTaskConstruction/ProductPlanning/ProductPlanner.swift index d6e50110..5d3cbfa9 100644 --- a/Sources/SWBTaskConstruction/ProductPlanning/ProductPlanner.swift +++ b/Sources/SWBTaskConstruction/ProductPlanning/ProductPlanner.swift @@ -82,6 +82,7 @@ private struct WorkspaceProductPlanBuilder { HeadermapVFSTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts), PCHModuleMapTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts), BuildDependencyInfoTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts), + CASConfigFileTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts), ] + (globalProductPlan.planRequest.buildRequest.enableIndexBuildArena ? [IndexBuildVFSDirectoryRemapTaskProducer(context: globalTaskProducerContext)] : []) for taskProducerExtension in await taskProducerExtensions(globalTaskProducerContext.workspaceContext) { diff --git a/Sources/SWBTaskConstruction/TaskProducers/WorkspaceTaskProducers/CASConfigFileTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/WorkspaceTaskProducers/CASConfigFileTaskProducer.swift new file mode 100644 index 00000000..906b1adb --- /dev/null +++ b/Sources/SWBTaskConstruction/TaskProducers/WorkspaceTaskProducers/CASConfigFileTaskProducer.swift @@ -0,0 +1,70 @@ +//===----------------------------------------------------------------------===// +// +// 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 SWBCore +import SWBUtil +import SWBMacro +import Foundation +import SWBProtocol + +final class CASConfigFileTaskProducer: StandardTaskProducer, TaskProducer { + private let targetContexts: [TaskProducerContext] + + init(context globalContext: TaskProducerContext, targetContexts: [TaskProducerContext]) { + self.targetContexts = targetContexts + super.init(globalContext) + } + + func generateTasks() async -> [any SWBCore.PlannedTask] { + var tasks = [any PlannedTask]() + do { + let casConfigFiles = try Dictionary(try await targetContexts.concurrentMap(maximumParallelism: 100) { (targetContext: TaskProducerContext) async throws -> (Path, ByteString)? in + let scope = targetContext.settings.globalScope + + // If compilation caching is not on, then there is no file to write. + // The condition here is more relax than the actual check in the compile task generation + // since it won't hurt if the file is not used. + guard scope.evaluate(BuiltinMacros.CLANG_ENABLE_COMPILE_CACHE) || scope.evaluate(BuiltinMacros.SWIFT_ENABLE_COMPILE_CACHE) else { + return nil + } + + // FIXME: we need consistent CAS configuration across all languages. + if !scope.evaluate(BuiltinMacros.COMPILATION_CACHE_REMOTE_SERVICE_PATH).isEmpty && !scope.evaluate(BuiltinMacros.COMPILATION_CACHE_REMOTE_SUPPORTED_LANGUAGES).isEmpty { + return nil + } + + let casOpts = try CASOptions.create(scope, .compiler(.other(dialectName: "swift"))) + struct CASConfig: Encodable { + let CASPath: String + let PluginPath: String? + } + let content = try JSONEncoder().encode(CASConfig(CASPath: casOpts.casPath.str, PluginPath: casOpts.pluginPath?.str)) + let path = scope.evaluate(BuiltinMacros.TARGET_TEMP_DIR).join(".cas-config") + return (path, ByteString(content)) + }.compactMap { $0 }, uniquingKeysWith: { first, second in + guard first == second else { + throw StubError.error("Unexpected difference in CAS config file.\nPath: \(first.asString)\nContent:\(second.asString)") + } + return first + }) + + for (configFilePath, configFileContent) in casConfigFiles { + await appendGeneratedTasks(&tasks) { delegate in + context.writeFileSpec.constructFileTasks(CommandBuildContext(producer: context, scope: context.settings.globalScope, inputs: [], output: configFilePath), delegate, contents: configFileContent, permissions: nil, preparesForIndexing: true, additionalTaskOrderingOptions: [.immediate]) + } + } + } catch { + self.context.error(error.localizedDescription) + } + return tasks + } +} diff --git a/Tests/SWBBuildSystemTests/ClangCompilationCachingTests.swift b/Tests/SWBBuildSystemTests/ClangCompilationCachingTests.swift index a1678a3b..d6fc6f7b 100644 --- a/Tests/SWBBuildSystemTests/ClangCompilationCachingTests.swift +++ b/Tests/SWBBuildSystemTests/ClangCompilationCachingTests.swift @@ -246,6 +246,13 @@ fileprivate struct ClangCompilationCachingTests: CoreBasedTests { } #expect(try readMetrics("one") == #"{"global":{"clangCacheHits":0,"clangCacheMisses":1,"swiftCacheHits":0,"swiftCacheMisses":0},"tasks":{"CompileC":{"cacheMisses":1,"headerDependenciesNotValidatedTasks":1,"moduleDependenciesNotValidatedTasks":1}}}"#) + let CASConfigPath = tmpDirPath.join("Test/aProject/build/aProject.build/Debug\(runDestination == .macOS ? "": "-" + runDestination.platform)/Library.build/.cas-config") + + #expect(try tester.fs.read(CASConfigPath).asString.contains("\"CASPath\":")) + if usePlugin { + #expect(try tester.fs.read(CASConfigPath).asString.contains("\"PluginPath\":")) + } + // Touch the source file to trigger a new scan. try await tester.fs.updateTimestamp(testWorkspace.sourceRoot.join("aProject/file.c")) diff --git a/Tests/SWBBuildSystemTests/SwiftCompilationCachingTests.swift b/Tests/SWBBuildSystemTests/SwiftCompilationCachingTests.swift index ff197e51..173ab100 100644 --- a/Tests/SWBBuildSystemTests/SwiftCompilationCachingTests.swift +++ b/Tests/SWBBuildSystemTests/SwiftCompilationCachingTests.swift @@ -116,6 +116,8 @@ fileprivate struct SwiftCompilationCachingTests: CoreBasedTests { } #expect(try readMetrics("one").contains("\"swiftCacheHits\":0,\"swiftCacheMisses\":\(numCompile)")) + #expect(try tester.fs.read(tmpDirPath.join("Test/aProject/build/aProject.build/Debug-iphoneos/Application.build/.cas-config")).asString.contains("\"CASPath\":")) + // touch a file, clean build folder, and rebuild. try await tester.fs.updateTimestamp(testWorkspace.sourceRoot.join("aProject/App.swift")) try await tester.checkBuild(runDestination: .anyiOSDevice, buildCommand: .cleanBuildFolder(style: .regular), body: { _ in })