-
Notifications
You must be signed in to change notification settings - Fork 83
/
Copy pathCoreDataCompiler.swift
100 lines (83 loc) · 5.24 KB
/
CoreDataCompiler.swift
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
//===----------------------------------------------------------------------===//
//
// 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 SWBUtil
public import SWBCore
import SWBMacro
public final class CoreDataModelCompilerSpec : GenericCompilerSpec, SpecIdentifierType, @unchecked Sendable {
public static let identifier = "com.apple.compilers.model.coredata"
public override var supportsInstallHeaders: Bool {
return true
}
public override var supportsInstallAPI: Bool {
return true
}
public override func constructTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async {
// Construct the momc task.
await constructCoreDataModelCompilerTasks(cbc, delegate)
// Construct the code generation task.
await constructCoreDataCodeGenerationTasks(cbc, delegate)
}
private func constructCoreDataModelCompilerTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async {
if cbc.scope.evaluate(BuiltinMacros.PACKAGE_RESOURCE_TARGET_KIND) == .regular {
return
}
let components = cbc.scope.evaluate(BuiltinMacros.BUILD_COMPONENTS)
if !components.contains("build") { return }
// Invoke the generic implementation in CommandLineToolSpec to generate the momc task using the xcspec.
await constructTasks(cbc, delegate, specialArgs: [])
}
private func constructCoreDataCodeGenerationTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async {
if cbc.scope.evaluate(BuiltinMacros.PACKAGE_RESOURCE_TARGET_KIND) == .resource {
return
}
// Compute the output directory.
let input = cbc.input
let modelName = input.absolutePath.basenameWithoutSuffix
let outputDir = cbc.scope.evaluate(BuiltinMacros.DERIVED_FILE_DIR).join("CoreDataGenerated").join(modelName).normalize()
let inputs = [input.absolutePath]
let ruleInfo = ["DataModelCodegen", input.absolutePath.str]
var commandLine = await commandLineFromTemplate(cbc, delegate, optionContext: discoveredCommandLineToolSpecInfo(cbc.producer, cbc.scope, delegate)).map(\.asString)
commandLine.insert(contentsOf: ["--action", "generate"] + (cbc.scope.evaluate(BuiltinMacros.SWIFT_VERSION).nilIfEmpty.map { ["--swift-version", $0] } ?? []), at: 1)
commandLine[commandLine.count - 1] = outputDir.str
// Ask the client delegate for the list of paths of generated files.
let generatedFiles: [Path]
do {
// Mark the entire directory structure as being watched by the build system.
delegate.access(path: input.absolutePath)
generatedFiles = try await generatedFilePaths(cbc, delegate, commandLine: [commandLine[0]] + ["--dry-run"] + commandLine[1...], workingDirectory: cbc.producer.defaultWorkingDirectory.str, environment: self.environmentFromSpec(cbc, delegate).bindingsDictionary, executionDescription: "Compute data model \(input.absolutePath.basename) code generation output paths") { output in
return output.unsafeStringValue.split(separator: "\n").map(Path.init).map { $0.prependingPrivatePrefixIfNeeded(otherPath: outputDir) }
}
guard !generatedFiles.isEmpty else {
// If we were given an empty list of generated files, then there were just no files to be generated, so we return.
// FIXME: Should we emit a generic error in this case? Should the code generator ever return no files to be generated?
return
}
} catch {
delegate.error("Could not determine generated file paths for Core Data code generation: \(error)")
return
}
// If we got this far, then we know we have a non-empty list of generated files.
let outputs = generatedFiles
// Declare the output files so they can be processed by the build phase which called us.
for output in outputs {
delegate.declareOutput(FileToBuild(absolutePath: output, inferringTypeUsing: cbc.producer))
}
delegate.createTask(type: self, ruleInfo: ruleInfo, commandLine: commandLine, environment: environmentFromSpec(cbc, delegate), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: inputs.map { delegate.createDirectoryTreeNode($0) }, outputs: outputs.map { delegate.createNode($0) }, execDescription: "Generate code for data model \(input.absolutePath.basename)", preparesForIndexing: true, enableSandboxing: enableSandboxing)
// Also add the generated headers to the generated files headermap.
for output in outputs {
// Somehow the file type specification infrastructure doesn't define an 'isHeader' property, so we look at the file extension.
if output.fileExtension == "h" {
delegate.declareGeneratedSourceFile(output)
}
}
}
}