Skip to content

Commit ff839fb

Browse files
committed
[WIP] Add new clang build setting EMIT_COMPILER_SOURCE_METADATA that
passes -fdiagnostic-add-output=sarif:file=<path>. The flag emits a SARIF log during the compilation which contains diagnostic information. The path is currently fixed to <object_file_path>.source-metadata.json
1 parent 9fdf0f2 commit ff839fb

File tree

4 files changed

+111
-8
lines changed

4 files changed

+111
-8
lines changed

Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -591,10 +591,10 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
591591

592592
required init() { }
593593

594-
func getStandardFlags(_ spec: ClangCompilerSpec, producer: any CommandProducer, scope: MacroEvaluationScope, optionContext: (any BuildOptionGenerationContext)?, delegate: any TaskGenerationDelegate, inputFileType: FileTypeSpec) -> ConstantFlags {
594+
func getStandardFlags(_ spec: ClangCompilerSpec, cbc: CommandBuildContext, optionContext: (any BuildOptionGenerationContext)?, delegate: any TaskGenerationDelegate, inputFileType: FileTypeSpec) -> ConstantFlags {
595595
// This cache is per-producer, so it is guaranteed to be invariant based on that.
596-
constantFlagsCache.getOrInsert(ConstantFlagsKey(scope: scope, inputFileType: inputFileType)) {
597-
return spec.standardFlags(producer, scope: scope, optionContext: optionContext, delegate: delegate, inputFileType: inputFileType)
596+
constantFlagsCache.getOrInsert(ConstantFlagsKey(scope: cbc.scope, inputFileType: inputFileType)) {
597+
return spec.standardFlags(cbc, optionContext: optionContext, delegate: delegate, inputFileType: inputFileType)
598598
}
599599
}
600600
}
@@ -646,8 +646,9 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
646646
super.init(parser, basedOnSpec, isGeneric: false)
647647
}
648648

649-
private func standardFlags(_ producer: any CommandProducer, scope: MacroEvaluationScope, optionContext: (any BuildOptionGenerationContext)?, delegate: any TaskGenerationDelegate, inputFileType: FileTypeSpec) -> ConstantFlags {
649+
private func standardFlags(_ cbc: CommandBuildContext, optionContext: (any BuildOptionGenerationContext)?, delegate: any TaskGenerationDelegate, inputFileType: FileTypeSpec) -> ConstantFlags {
650650
var commandLine = Array<String>()
651+
let producer = cbc.producer, scope = cbc.scope
651652

652653
// Add the arguments from the specification.
653654
commandLine += self.commandLineFromOptions(producer, scope: scope, inputFileType: inputFileType, optionContext: optionContext, buildOptionsFilter: .specOnly, lookup: { declaration in
@@ -657,7 +658,7 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
657658
return BuiltinMacros.namespace.parseString("NO")
658659
}
659660
}
660-
return nil
661+
return self.lookup(declaration, cbc, delegate)
661662
}).map(\.asString)
662663

663664
// Add the common header search paths.
@@ -1136,7 +1137,8 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
11361137
//
11371138
// FIXME: Eventually we should just apply this optimization to all generic specs, and then find a way to piggy back on that.
11381139
let dataCache = cbc.producer.getSpecDataCache(self, cacheType: DataCache.self)
1139-
let constantFlags = dataCache.getStandardFlags(self, producer: cbc.producer, scope: cbc.scope, optionContext: clangInfo, delegate: delegate, inputFileType: resolvedInputFileType)
1140+
let cbcWithOutput = cbc.outputs.isEmpty ? cbc.appendingOutputs([outputNode.path]) : cbc
1141+
let constantFlags = dataCache.getStandardFlags(self, cbc: cbcWithOutput, optionContext: clangInfo, delegate: delegate, inputFileType: resolvedInputFileType)
11401142
commandLine += constantFlags.flags
11411143
let responseFileAdditionalOutput = constantFlags.responseFileMapping.keys.sorted().map({"Using response file: \($0.str)"})
11421144
additionalOutput.append(contentsOf: responseFileAdditionalOutput)
@@ -1157,7 +1159,6 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
11571159
inputDeps.append(contentsOf: constantFlags.headerSearchPaths.inputPaths)
11581160
#endif
11591161

1160-
let cbcWithOutput = cbc.outputs.isEmpty ? cbc.appendingOutputs([outputNode.path]) : cbc
11611162
commandLine += self.commandLineFromOptions(cbc.producer, scope: cbc.scope, inputFileType: resolvedInputFileType, optionContext: clangInfo, buildOptionsFilter: .extendedOnly, lookup: {
11621163
self.lookup($0, cbcWithOutput, delegate)
11631164
}).map(\.asString)
@@ -1681,7 +1682,7 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
16811682

16821683
// Add “standard flags”, which are ones that depend only on the variant, architecture, and language (in addition to the identifier, of course).
16831684
let dataCache = cbc.producer.getSpecDataCache(self, cacheType: DataCache.self)
1684-
let constantFlags = dataCache.getStandardFlags(self, producer: cbc.producer, scope: cbc.scope, optionContext: clangInfo, delegate: delegate, inputFileType: inputFileType)
1685+
let constantFlags = dataCache.getStandardFlags(self, cbc: cbc, optionContext: clangInfo, delegate: delegate, inputFileType: inputFileType)
16851686
let responseFileAdditionalOutput = constantFlags.responseFileMapping.keys.sorted().map({"Using response file: \($0.str)"})
16861687
commandLine += constantFlags.flags
16871688

Sources/SWBUniversalPlatform/Specs/Clang.xcspec

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,24 @@
109109
NO = ();
110110
};
111111
},
112+
{
113+
Name = "EMIT_COMPILER_SOURCE_METADATA";
114+
Type = Boolean;
115+
DefaultValue = No;
116+
},
117+
{
118+
Name = "__CLANG_SOURCE_METADATA_PATH";
119+
Type = Path;
120+
Condition = "$(EMIT_COMPILER_SOURCE_METADATA)";
121+
DefaultValue = "$(OutputPath).source-metadata.json";
122+
CommandLinePrefixFlag = "-fdiagnostics-add-output=sarif:file=";
123+
OutputDependencies = (
124+
{
125+
Path = "$(__CLANG_SOURCE_METADATA_PATH)";
126+
FileType = "text.json.compiler-metadata.source";
127+
},
128+
);
129+
},
112130
{
113131
Name = "CLANG_MACRO_BACKTRACE_LIMIT";
114132
Type = String;

Sources/SWBUniversalPlatform/Specs/StandardFileTypes.xcspec

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,4 +1720,11 @@
17201720
CanSetIncludeInIndex = YES;
17211721
UTI = "public.protobuf-source";
17221722
},
1723+
{
1724+
Identifier = text.json.compiler-metadata.source;
1725+
Type = FileType;
1726+
Name = "Source metadata emitted from compiler";
1727+
UTI = "com.apple.compiler-metadata.source";
1728+
BasedOn = text.json;
1729+
},
17231730
)

Tests/SWBTaskConstructionTests/TaskConstructionTests.swift

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9512,4 +9512,81 @@ fileprivate struct TaskConstructionTests: CoreBasedTests {
95129512
}
95139513
}
95149514
}
9515+
9516+
@Test(.requireSDKs(.macOS))
9517+
func testSourceMetadata() async throws {
9518+
try await withTemporaryDirectory { (tmpDir: Path) async throws -> Void in
9519+
let sources = [
9520+
"SourceFile0.c",
9521+
"SourceFile1.mm",
9522+
"SourceFile2.m",
9523+
"SourceFile3.cpp"
9524+
]
9525+
9526+
let testProject = TestProject(
9527+
"aProject",
9528+
sourceRoot: tmpDir,
9529+
groupTree: TestGroup(
9530+
"SomeFiles", path: "Sources",
9531+
children: sources.map{TestFile($0)}
9532+
),
9533+
buildConfigurations: [
9534+
TestBuildConfiguration("Debug")
9535+
],
9536+
targets: [
9537+
TestStandardTarget(
9538+
"AppTarget",
9539+
type: .application,
9540+
buildConfigurations: [
9541+
TestBuildConfiguration("Debug", buildSettings: [
9542+
"TARGET_BUILD_DIR": "/tmp/SomeFiles.dst",
9543+
"GENERATE_INFOPLIST_FILE": "YES",
9544+
"PRODUCT_NAME": "$(TARGET_NAME)",
9545+
"ARCHS": "x86_64 arm64",
9546+
"EMIT_COMPILER_SOURCE_METADATA": "YES",
9547+
"COMPILER_SOURCE_METADATA_LIST": "/tmp/test.json"
9548+
])
9549+
],
9550+
buildPhases: [
9551+
TestSourcesBuildPhase(sources.map{TestBuildFile($0)})
9552+
]
9553+
)]
9554+
)
9555+
9556+
let fs = PseudoFS()
9557+
9558+
let core = try await getCore()
9559+
let tester = try TaskConstructionTester(core, testProject)
9560+
9561+
await tester.checkBuild(BuildParameters(configuration: "Debug"), runDestination: .anyMac, fs: fs) { results -> Void in
9562+
results.checkTarget("AppTarget") { target -> Void in
9563+
let command = "-fdiagnostics-add-output=sarif:file="
9564+
let buildPath = tmpDir.join("build/aProject.build/Debug/AppTarget.build/Objects-normal/")
9565+
for arch in ["x86_64", "arm64"] {
9566+
let metadataPath = buildPath.join(arch)
9567+
let inputs = sources.map{metadataPath.join(Path($0).basenameWithoutSuffix + ".o.source-metadata.json").str}
9568+
9569+
for (source, input) in zip(sources, inputs) {
9570+
results.checkTask(.matchTarget(target), .matchRuleType("CompileC"), .matchRuleItemBasename(source), .matchRuleItem(arch), body: { task in
9571+
task.checkCommandLineContains([command + input])
9572+
task.checkInputs([
9573+
.path(tmpDir.join("Sources").join(source).str),
9574+
.namePattern(.and(.prefix("target-"), .suffix("-generated-headers"))),
9575+
.namePattern(.and(.prefix("target-"), .suffix("-swift-generated-headers"))),
9576+
.namePattern(.and(.prefix("target-"), .suffix("-ModuleVerifierTaskProducer"))),
9577+
.namePattern(.and(.prefix("target-"), .suffix("-begin-compiling"))),
9578+
.name("WorkspaceHeaderMapVFSFilesWritten"),
9579+
])
9580+
task.checkOutputs([
9581+
.path(buildPath.join(arch).join(Path(source).basenameWithoutSuffix + ".o").str),
9582+
.path(metadataPath.join(Path(source).basenameWithoutSuffix + ".o.source-metadata.json").str)
9583+
])
9584+
})
9585+
9586+
}
9587+
}
9588+
}
9589+
}
9590+
}
9591+
}
95159592
}

0 commit comments

Comments
 (0)