From dc2fed2287316e987cd92ee19d4f9eb4596ae04f Mon Sep 17 00:00:00 2001
From: Jake Petroules <jake.petroules@apple.com>
Date: Wed, 16 Apr 2025 18:09:55 -0700
Subject: [PATCH] Add support for testable executables on Windows

This uses the /ALTERNATENAME flag to link.exe, which is roughly equivalent to -alias/--defsym. This has been verified to work in a sample project.

See https://devblogs.microsoft.com/oldnewthing/20200731-00/ for more info.

Closes #6367
---
 Sources/Build/BuildPlan/BuildPlan.swift | 20 ++++++++++++++++++++
 Tests/BuildTests/BuildPlanTests.swift   |  4 ++--
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift
index 01ffe8ec665..d2c27d849cf 100644
--- a/Sources/Build/BuildPlan/BuildPlan.swift
+++ b/Sources/Build/BuildPlan/BuildPlan.swift
@@ -151,12 +151,32 @@ extension BuildParameters {
             args = ["-alias", "_\(target.c99name)_main", "_main"]
         case .elf:
             args = ["--defsym", "main=\(target.c99name)_main"]
+        case .coff:
+            // If the user is specifying a custom entry point name that isn't "main", assume they may be setting WinMain or wWinMain
+            // and don't do any modifications ourselves. In that case the linker will infer the WINDOWS subsystem and call WinMainCRTStartup,
+            // which will then call the custom entry point. And WinMain/wWinMain != main, so this still won't run into duplicate symbol
+            // issues when called from a test target, which always uses main.
+            if let customEntryPointFunctionName = findCustomEntryPointFunctionName(of: target), customEntryPointFunctionName != "main" {
+                return nil
+            }
+            args = ["/ALTERNATENAME:main=\(target.c99name)_main", "/SUBSYSTEM:CONSOLE"]
         default:
             return nil
         }
         return args.asSwiftcLinkerFlags()
     }
 
+    private func findCustomEntryPointFunctionName(of target: ResolvedModule) -> String? {
+        let flags = createScope(for: target).evaluate(.OTHER_SWIFT_FLAGS)
+        var it = flags.makeIterator()
+        while let value = it.next() {
+            if value == "-Xfrontend" && it.next() == "-entry-point-function-name" && it.next() == "-Xfrontend" {
+                return it.next()
+            }
+        }
+        return nil
+    }
+
     /// Returns the scoped view of build settings for a given target.
     func createScope(for target: ResolvedModule) -> BuildSettings.Scope {
         BuildSettings.Scope(target.underlying.buildSettings, environment: buildEnvironment)
diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift
index 6d8741e7ad4..65fc0b5b206 100644
--- a/Tests/BuildTests/BuildPlanTests.swift
+++ b/Tests/BuildTests/BuildPlanTests.swift
@@ -3875,7 +3875,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
                 observabilityScope: observability.topScope
             ))
         }
-        let supportingTriples: [Basics.Triple] = [.x86_64Linux, .x86_64MacOS]
+        let supportingTriples: [Basics.Triple] = [.x86_64Linux, .x86_64MacOS, .x86_64Windows]
         for triple in supportingTriples {
             let result = try await createResult(for: triple)
             let exe = try result.moduleBuildDescription(for: "exe").swift().compileArguments()
@@ -3884,7 +3884,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
             XCTAssertMatch(linkExe, [.contains("exe_main")])
         }
 
-        let unsupportingTriples: [Basics.Triple] = [.wasi, .windows]
+        let unsupportingTriples: [Basics.Triple] = [.wasi]
         for triple in unsupportingTriples {
             let result = try await createResult(for: triple)
             let exe = try result.moduleBuildDescription(for: "exe").swift().compileArguments()