Skip to content

Commit ddbb57d

Browse files
committed
Add codeview debug format for windows
- update xcspec for Windows to support codeview debug format - update ObjectLibraryAssembler to support files with the same basename by detecting the duplicates and renaming them. closes: #560
1 parent c45c6e3 commit ddbb57d

7 files changed

Lines changed: 295 additions & 10 deletions

File tree

Sources/SWBTaskExecution/TaskActions/ObjectLibraryAssemblerTaskAction.swift

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,43 @@ public final class ObjectLibraryAssemblerTaskAction: TaskAction {
3535
do {
3636
let options = try Options.parse(Array(task.commandLineAsStrings.dropFirst()))
3737
try? executionDelegate.fs.remove(options.output)
38-
try executionDelegate.fs.createDirectory(options.output)
39-
_ = try await options.inputs.concurrentMap(maximumParallelism: 10) { input in
40-
try executionDelegate.fs.copy(input, to: options.output.join(input.basename))
38+
try executionDelegate.fs.createDirectory(options.output, recursive: true)
39+
40+
// Track basename usage for duplicate detection and build destination mappings
41+
var basenameCount: [String: Int] = [:]
42+
var inputsWithDestinations: [(source: Path, destination: String)] = []
43+
44+
// Process each input to determine its destination name
45+
for input in options.inputs {
46+
let basename = input.basename
47+
let count = basenameCount[basename, default: 0]
48+
basenameCount[basename] = count + 1
49+
50+
let destinationName: String
51+
if count == 0 {
52+
// First occurrence, use original name
53+
destinationName = basename
54+
} else {
55+
// Duplicate detected, add suffix before extension
56+
let nameWithoutSuffix = input.withoutSuffix
57+
let suffix = input.fileSuffix // Includes the dot
58+
destinationName = "\(Path(nameWithoutSuffix).basename)-\(count)\(suffix)"
59+
}
60+
61+
inputsWithDestinations.append((source: input, destination: destinationName))
4162
}
42-
let args = options.inputs.map { options.output.join($0.basename).strWithPosixSlashes }
63+
64+
// Copy files with their resolved destination names
65+
for item in inputsWithDestinations {
66+
let destinationPath = options.output.join(item.destination)
67+
try executionDelegate.fs.copy(item.source, to: destinationPath)
68+
}
69+
70+
// Build args array with flattened paths
71+
let args = inputsWithDestinations.map { item in
72+
options.output.join(item.destination).strWithPosixSlashes
73+
}
74+
4375
try executionDelegate.fs.write(options.output.join("args.resp"), contents: ByteString(encodingAsUTF8: ResponseFiles.responseFileContents(args: args, format: options.linkerResponseFileFormat)))
4476
return .succeeded
4577
} catch {

Sources/SWBWindowsPlatform/Plugin.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ struct WindowsSDKRegistryExtension: SDKRegistryExtension {
184184
}
185185
let testingLibraryPath = windowsPlatform.path.join("Developer").join("Library")
186186
let defaultProperties: [String: PropertyListItem] = [
187-
"GCC_GENERATE_DEBUGGING_SYMBOLS": .plString("NO"),
188187
"LD_DEPENDENCY_INFO_FILE": .plString(""),
189188
"PRELINK_DEPENDENCY_INFO_FILE": .plString(""),
190189

Sources/SWBWindowsPlatform/Specs/WindowsCompile.xcspec

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,31 @@
6060
Description = "Apple Clang compiler (Windows)";
6161
IsAbstract = YES;
6262
ShowInCompilerSelectionPopup = NO;
63+
Options = (
64+
{
65+
Name = "GCC_DEBUG_INFORMATION_FORMAT";
66+
Type = Enumeration;
67+
Values = (
68+
dwarf,
69+
"dwarf-with-dsym",
70+
"codeview",
71+
);
72+
CommandLineArgs = {
73+
dwarf = (
74+
"-g",
75+
);
76+
"dwarf-with-dsym" = (
77+
"-g",
78+
);
79+
"codeview" = (
80+
"-g", "-gcodeview",
81+
);
82+
"<<otherwise>>" = ();
83+
};
84+
DefaultValue = "$(DEBUG_INFORMATION_FORMAT)";
85+
Condition = "$(GCC_GENERATE_DEBUGGING_SYMBOLS)";
86+
},
87+
);
6388
},
6489
{
6590
Domain = windows;
@@ -106,8 +131,27 @@
106131
CommandLineArgs = ("-static");
107132
},
108133
{
109-
Name = SYSROOT;
110-
Type = Path;
134+
Name = "SWIFT_DEBUG_INFORMATION_FORMAT";
135+
Type = Enumeration;
136+
Values = (
137+
dwarf,
138+
"dwarf-with-dsym",
139+
"codeview",
140+
);
141+
CommandLineArgs = {
142+
dwarf = (
143+
"-g",
144+
);
145+
"dwarf-with-dsym" = (
146+
"-g",
147+
);
148+
"codeview" = (
149+
"-g", "-debug-info-format=codeview",
150+
);
151+
"<<otherwise>>" = ();
152+
};
153+
DefaultValue = "$(DEBUG_INFORMATION_FORMAT)";
154+
Condition = "$(GCC_GENERATE_DEBUGGING_SYMBOLS)";
111155
},
112156
);
113157
},

Tests/SWBBuildSystemTests/BuildCommandTests.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ fileprivate struct BuildCommandTests: CoreBasedTests {
4747
)
4848
]
4949
)
50-
let tester = try await BuildOperationTester(getCore(), testWorkspace, simulated: false)
50+
let core = try await getCore()
51+
let tester = try await BuildOperationTester(core, testWorkspace, simulated: false)
5152

5253
// Create the input files.
5354
let cFile = testWorkspace.sourceRoot.join("aProject/CFile.c")
@@ -74,7 +75,7 @@ fileprivate struct BuildCommandTests: CoreBasedTests {
7475
results.consumeTasksMatchingRuleTypes(excludedTypes)
7576
results.checkTaskExists(.matchRule(["SwiftCompile", "normal", results.runDestinationTargetArchitecture, "Compiling \(swiftFile.basename)", swiftFile.str]))
7677
results.checkTaskExists(.matchRule(["SwiftEmitModule", "normal", results.runDestinationTargetArchitecture, "Emitting module for aLibrary"]))
77-
if runDestination == .linux { // FIXME: This needs to be investigated... We should be able to use core.hostOperatingSystem.imageFormat.requiresSwiftModulewrap to test for this, but on Windows using this causes the test to fail as the SwiftModuleWrap does not seem to be added.
78+
if try await core.hostOperatingSystem.imageFormat.requiresSwiftModulewrap {
7879
results.checkTaskExists(.matchRule(["SwiftModuleWrap", "normal", results.runDestinationTargetArchitecture, "Wrapping Swift module aLibrary"]))
7980
}
8081
results.checkNoTask()

Tests/SWBBuildSystemTests/ClangExplicitModulesTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,7 @@ fileprivate struct ClangExplicitModulesTests: CoreBasedTests {
11741174
"CLANG_ENABLE_MODULES": "YES",
11751175
"_EXPERIMENTAL_CLANG_EXPLICIT_MODULES": "YES",
11761176
"CLANG_EXPLICIT_MODULES_OUTPUT_PATH": "$OBJROOT/ExplicitModules/$TARGET_NAME",
1177+
"GCC_GENERATE_DEBUGGING_SYMBOLS": "NO",
11771178
])],
11781179
targets: [
11791180
TestStandardTarget(
@@ -1303,6 +1304,7 @@ fileprivate struct ClangExplicitModulesTests: CoreBasedTests {
13031304
"PRODUCT_NAME": "$(TARGET_NAME)",
13041305
"CLANG_ENABLE_MODULES": "YES",
13051306
"_EXPERIMENTAL_CLANG_EXPLICIT_MODULES": "YES",
1307+
"GCC_GENERATE_DEBUGGING_SYMBOLS": "NO",
13061308
])],
13071309
targets: [
13081310
TestStandardTarget(
@@ -1402,6 +1404,7 @@ fileprivate struct ClangExplicitModulesTests: CoreBasedTests {
14021404
"CLANG_ENABLE_MODULES": "YES",
14031405
"_EXPERIMENTAL_CLANG_EXPLICIT_MODULES": "YES",
14041406
"CLANG_EXPLICIT_MODULES_OUTPUT_PATH": "\(tmpDir.join("clangmodules").str)",
1407+
"GCC_GENERATE_DEBUGGING_SYMBOLS": "NO",
14051408
])],
14061409
targets: [
14071410
TestStandardTarget(
@@ -1426,6 +1429,7 @@ fileprivate struct ClangExplicitModulesTests: CoreBasedTests {
14261429
"_EXPERIMENTAL_CLANG_EXPLICIT_MODULES": "YES",
14271430
"HEADER_SEARCH_PATHS": "$(inherited) \(tmpDir.join("Test").join("aProject").strWithPosixSlashes)",
14281431
"CLANG_EXPLICIT_MODULES_OUTPUT_PATH": "\(tmpDir.join("clangmodules").strWithPosixSlashes)",
1432+
"GCC_GENERATE_DEBUGGING_SYMBOLS": "NO",
14291433
])],
14301434
targets: [
14311435
TestStandardTarget(

Tests/SWBTaskConstructionTests/DebugInformationTests.swift

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import SWBUtil
2222
@Suite
2323
fileprivate struct DebugInformationTests: CoreBasedTests {
2424
/// Test the different DWARF version formats we support.
25-
@Test(.requireSDKs(.host), .skipHostOS(.windows))
25+
@Test(.requireSDKs(.host))
2626
func debugInformationVersion() async throws {
2727
let testProject = try await TestProject(
2828
"aProject",
@@ -366,4 +366,55 @@ fileprivate struct DebugInformationTests: CoreBasedTests {
366366
}
367367
}
368368
}
369+
370+
/// Test that on Windows we can set the DEBUG_INFORMATION_FORMAT build setting to "codeview".
371+
@Test(.requireSDKs(.windows))
372+
func codeViewDebugInformation() async throws {
373+
let testProject = try await TestProject(
374+
"aProject",
375+
groupTree: TestGroup(
376+
"SomeFiles", path: "Sources",
377+
children: [
378+
TestFile("CFile.c"),
379+
TestFile("SwiftFile.swift"),
380+
]),
381+
buildConfigurations: [
382+
TestBuildConfiguration(
383+
"Config",
384+
buildSettings: [
385+
"DEBUG_INFORMATION_FORMAT": "codeview",
386+
"GENERATE_INFOPLIST_FILE": "YES",
387+
"PRODUCT_NAME": "$(TARGET_NAME)",
388+
"SWIFT_EXEC": swiftCompilerPath.str,
389+
"SWIFT_VERSION": swiftVersion,
390+
]),
391+
],
392+
targets: [
393+
TestStandardTarget(
394+
"CoreFoo", type: .framework,
395+
buildPhases: [
396+
TestSourcesBuildPhase([
397+
"CFile.c",
398+
"SwiftFile.swift",
399+
]),
400+
]),
401+
])
402+
let tester = try await TaskConstructionTester(getCore(), testProject)
403+
404+
// Test that CodeView debug format is applied correctly.
405+
await tester.checkBuild(BuildParameters(configuration: "Config"), runDestination: .host) { results in
406+
// Check clang - should have CodeView debug flags.
407+
results.checkTask(.matchRuleType("CompileC")) { task in
408+
task.checkCommandLineContains(["-gcodeview"])
409+
}
410+
411+
// Check swiftc - should have CodeView debug flags.
412+
results.checkTask(.matchRuleType("SwiftDriver Compilation")) { task in
413+
task.checkCommandLineContains(["-g", "-debug-info-format=codeview"])
414+
}
415+
416+
// Check there are no diagnostics.
417+
results.checkNoDiagnostics()
418+
}
419+
}
369420
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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 Foundation
14+
import Testing
15+
import SWBCore
16+
import SWBTaskExecution
17+
import SWBTestSupport
18+
import SWBUtil
19+
20+
@Suite
21+
fileprivate struct ObjectLibraryAssemblerTaskActionTests {
22+
@Test
23+
func duplicateFileHandling() async throws {
24+
// Create input files with duplicate basenames (all named file.o)
25+
let inputOne = Path.root.join("input1").join("file.o")
26+
let inputTwo = Path.root.join("input2").join("file.o")
27+
let inputThree = Path.root.join("input3").join("file.o")
28+
let inputUnique = Path.root.join("unique.o")
29+
let output = Path.root.join("output")
30+
31+
let executionDelegate = MockExecutionDelegate()
32+
try executionDelegate.fs.createDirectory(inputOne.dirname, recursive: true)
33+
try executionDelegate.fs.createDirectory(inputTwo.dirname, recursive: true)
34+
try executionDelegate.fs.createDirectory(inputThree.dirname, recursive: true)
35+
try executionDelegate.fs.write(inputOne, contents: ByteString(encodingAsUTF8: "one"))
36+
try executionDelegate.fs.write(inputTwo, contents: ByteString(encodingAsUTF8: "two"))
37+
try executionDelegate.fs.write(inputThree, contents: ByteString(encodingAsUTF8: "three"))
38+
try executionDelegate.fs.write(inputUnique, contents: ByteString(encodingAsUTF8: "unique"))
39+
40+
let outputDelegate = MockTaskOutputDelegate()
41+
42+
let commandLine = [
43+
"builtin-ObjectLibraryAssembler",
44+
"--linker-response-file-format",
45+
"unixShellQuotedSpaceSeparated",
46+
inputOne.str,
47+
inputTwo.str,
48+
inputThree.str,
49+
inputUnique.str,
50+
"--output",
51+
output.str
52+
].map { ByteString(encodingAsUTF8: $0) }
53+
54+
let inputs = [inputOne, inputTwo, inputThree, inputUnique].map { MakePlannedPathNode($0) }
55+
var builder = PlannedTaskBuilder(type: mockTaskType, ruleInfo: [], commandLine: commandLine.map { .literal($0) }, inputs: inputs, outputs: [MakePlannedPathNode(output)])
56+
let task = Task(&builder)
57+
58+
let result = await ObjectLibraryAssemblerTaskAction().performTaskAction(
59+
task,
60+
dynamicExecutionDelegate: MockDynamicTaskExecutionDelegate(),
61+
executionDelegate: executionDelegate,
62+
clientDelegate: MockTaskExecutionClientDelegate(),
63+
outputDelegate: outputDelegate
64+
)
65+
66+
#expect(result == .succeeded, "Task should succeed")
67+
#expect(outputDelegate.messages == [], "Should have no error messages")
68+
69+
// Verify the output directory was created
70+
#expect(executionDelegate.fs.exists(output), "Output directory should exist")
71+
72+
// Verify that files were copied with proper duplicate handling
73+
let copiedOriginal = output.join("file.o")
74+
let copiedDuplicate1 = output.join("file-1.o")
75+
let copiedDuplicate2 = output.join("file-2.o")
76+
let copiedUnique = output.join("unique.o")
77+
78+
#expect(executionDelegate.fs.exists(copiedOriginal), "First file.o should exist")
79+
#expect(executionDelegate.fs.exists(copiedDuplicate1), "file-1.o should exist")
80+
#expect(executionDelegate.fs.exists(copiedDuplicate2), "file-2.o should exist")
81+
#expect(executionDelegate.fs.exists(copiedUnique), "unique.o should exist")
82+
83+
// Verify content of copied files
84+
#expect(try executionDelegate.fs.read(copiedOriginal).asString == "one")
85+
#expect(try executionDelegate.fs.read(copiedDuplicate1).asString == "two")
86+
#expect(try executionDelegate.fs.read(copiedDuplicate2).asString == "three")
87+
#expect(try executionDelegate.fs.read(copiedUnique).asString == "unique")
88+
89+
// Verify the response file was created with correct paths
90+
let responseFile = output.join("args.resp")
91+
#expect(executionDelegate.fs.exists(responseFile), "Response file should exist")
92+
93+
let responseContent = try executionDelegate.fs.read(responseFile).asString
94+
#expect(responseContent.contains("file.o"), "Response should contain file.o")
95+
#expect(responseContent.contains("file-1.o"), "Response should contain file-1.o")
96+
#expect(responseContent.contains("file-2.o"), "Response should contain file-2.o")
97+
#expect(responseContent.contains("unique.o"), "Response should contain unique.o")
98+
}
99+
100+
@Test
101+
func noDuplicates() async throws {
102+
// Test that files with unique basenames are not renamed
103+
let inputOne = Path.root.join("a.o")
104+
let inputTwo = Path.root.join("b.o")
105+
let inputThree = Path.root.join("c.o")
106+
let output = Path.root.join("output")
107+
108+
let executionDelegate = MockExecutionDelegate()
109+
try executionDelegate.fs.write(inputOne, contents: ByteString(encodingAsUTF8: "a"))
110+
try executionDelegate.fs.write(inputTwo, contents: ByteString(encodingAsUTF8: "b"))
111+
try executionDelegate.fs.write(inputThree, contents: ByteString(encodingAsUTF8: "c"))
112+
113+
let outputDelegate = MockTaskOutputDelegate()
114+
115+
let commandLine = [
116+
"builtin-ObjectLibraryAssembler",
117+
"--linker-response-file-format",
118+
"unixShellQuotedSpaceSeparated",
119+
inputOne.str,
120+
inputTwo.str,
121+
inputThree.str,
122+
"--output",
123+
output.str
124+
].map { ByteString(encodingAsUTF8: $0) }
125+
126+
let inputs = [inputOne, inputTwo, inputThree].map { MakePlannedPathNode($0) }
127+
var builder = PlannedTaskBuilder(type: mockTaskType, ruleInfo: [], commandLine: commandLine.map { .literal($0) }, inputs: inputs, outputs: [MakePlannedPathNode(output)])
128+
let task = Task(&builder)
129+
130+
let result = await ObjectLibraryAssemblerTaskAction().performTaskAction(
131+
task,
132+
dynamicExecutionDelegate: MockDynamicTaskExecutionDelegate(),
133+
executionDelegate: executionDelegate,
134+
clientDelegate: MockTaskExecutionClientDelegate(),
135+
outputDelegate: outputDelegate
136+
)
137+
138+
#expect(result == .succeeded, "Task should succeed")
139+
140+
// Verify files are copied with their original names (no -N suffix)
141+
let copiedA = output.join("a.o")
142+
let copiedB = output.join("b.o")
143+
let copiedC = output.join("c.o")
144+
145+
#expect(executionDelegate.fs.exists(copiedA), "a.o should exist")
146+
#expect(executionDelegate.fs.exists(copiedB), "b.o should exist")
147+
#expect(executionDelegate.fs.exists(copiedC), "c.o should exist")
148+
149+
// Verify that no renamed versions exist
150+
#expect(!executionDelegate.fs.exists(output.join("a-1.o")), "a-1.o should not exist")
151+
#expect(!executionDelegate.fs.exists(output.join("b-1.o")), "b-1.o should not exist")
152+
#expect(!executionDelegate.fs.exists(output.join("c-1.o")), "c-1.o should not exist")
153+
}
154+
}

0 commit comments

Comments
 (0)