Skip to content

Commit 5701d61

Browse files
authored
Add initial support for generating non-Darwin test entrypoints (#390)
1 parent cb89d38 commit 5701d61

File tree

12 files changed

+388
-2
lines changed

12 files changed

+388
-2
lines changed

Package.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,14 @@ let package = Package(
231231
swiftSettings: swiftSettings(languageMode: .v6)),
232232
.target(
233233
name: "SWBUniversalPlatform",
234-
dependencies: ["SWBCore", "SWBMacro", "SWBUtil"],
234+
dependencies: [
235+
"SWBCore",
236+
"SWBMacro",
237+
"SWBUtil",
238+
"SWBTaskConstruction",
239+
"SWBTaskExecution",
240+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
241+
],
235242
exclude: ["CMakeLists.txt"],
236243
resources: [.process("Specs")],
237244
swiftSettings: swiftSettings(languageMode: .v6)),

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,8 @@ public final class BuiltinMacros {
721721
public static let GENERATE_MASTER_OBJECT_FILE = BuiltinMacros.declareBooleanMacro("GENERATE_MASTER_OBJECT_FILE")
722722
public static let GENERATE_PKGINFO_FILE = BuiltinMacros.declareBooleanMacro("GENERATE_PKGINFO_FILE")
723723
public static let GENERATE_RESOURCE_ACCESSORS = BuiltinMacros.declareBooleanMacro("GENERATE_RESOURCE_ACCESSORS")
724+
public static let GENERATE_TEST_ENTRY_POINT = BuiltinMacros.declareBooleanMacro("GENERATE_TEST_ENTRY_POINT")
725+
public static let GENERATED_TEST_ENTRY_POINT_PATH = BuiltinMacros.declarePathMacro("GENERATED_TEST_ENTRY_POINT_PATH")
724726
public static let GENERATE_TEXT_BASED_STUBS = BuiltinMacros.declareBooleanMacro("GENERATE_TEXT_BASED_STUBS")
725727
public static let GENERATE_INTERMEDIATE_TEXT_BASED_STUBS = BuiltinMacros.declareBooleanMacro("GENERATE_INTERMEDIATE_TEXT_BASED_STUBS")
726728
public static let GLOBAL_API_NOTES_PATH = BuiltinMacros.declareStringMacro("GLOBAL_API_NOTES_PATH")
@@ -1757,6 +1759,8 @@ public final class BuiltinMacros {
17571759
GENERATE_MASTER_OBJECT_FILE,
17581760
GENERATE_PKGINFO_FILE,
17591761
GENERATE_RESOURCE_ACCESSORS,
1762+
GENERATE_TEST_ENTRY_POINT,
1763+
GENERATED_TEST_ENTRY_POINT_PATH,
17601764
GENERATE_TEXT_BASED_STUBS,
17611765
GENERATE_INTERMEDIATE_TEXT_BASED_STUBS,
17621766
GID,

Sources/SWBGenericUnixPlatform/Specs/Unix.xcspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
SWIFT_FORCE_STATIC_LINK_STDLIB = NO;
5454
// Avoid warning for executable types
5555
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
56+
GENERATE_TEST_ENTRY_POINT = YES;
57+
GENERATED_TEST_ENTRY_POINT_PATH = "$(DERIVED_SOURCES_DIR)/test_entry_point.swift";
5658
};
5759
PackageTypes = (
5860
com.apple.package-type.mach-o-executable // default

Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,10 @@ final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBase
838838
result.append((embedInCodeAccessorResult.fileToBuild, embedInCodeAccessorResult.fileToBuildFileType, /* shouldUsePrefixHeader */ false))
839839
}
840840

841+
if scope.evaluate(BuiltinMacros.GENERATE_TEST_ENTRY_POINT) {
842+
result.append((scope.evaluate(BuiltinMacros.GENERATED_TEST_ENTRY_POINT_PATH), context.lookupFileType(fileName: "sourcecode.swift")!, /* shouldUsePrefixHeader */ false))
843+
}
844+
841845
return result
842846
}())
843847

Sources/SWBUniversalPlatform/CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,17 @@ add_library(SWBUniversalPlatform STATIC
3434
CppTool.swift
3535
DiffTool.swift
3636
LexCompiler.swift
37+
TestEntryPointGenerationTaskAction.swift
38+
TestEntryPointGenerationTool.swift
39+
TestEntryPointTaskProducer.swift
3740
YaccCompiler.swift
3841
Plugin.swift)
3942
target_link_libraries(SWBUniversalPlatform PUBLIC
4043
SWBCore
4144
SWBMacro
42-
SWBUtil)
45+
SWBUtil
46+
SWBTaskConstruction
47+
SWBTaskExecution
48+
ArgumentParser)
4349
target_sources(SWBUniversalPlatform PRIVATE
4450
"${CMAKE_CURRENT_BINARY_DIR}/resource_bundle_accessor.swift")

Sources/SWBUniversalPlatform/Plugin.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@
1313
public import SWBUtil
1414
import SWBCore
1515
import Foundation
16+
import SWBTaskConstruction
17+
import SWBTaskExecution
1618

1719
@PluginExtensionSystemActor public func initializePlugin(_ manager: PluginManager) {
1820
manager.register(UniversalPlatformSpecsExtension(), type: SpecificationsExtensionPoint.self)
21+
manager.register(UniversalPlatformTaskProducerExtension(), type: TaskProducerExtensionPoint.self)
22+
manager.register(UniversalPlatformTaskActionExtension(), type: TaskActionExtensionPoint.self)
1923
}
2024

2125
struct UniversalPlatformSpecsExtension: SpecificationsExtension {
@@ -26,6 +30,7 @@ struct UniversalPlatformSpecsExtension: SpecificationsExtension {
2630
CppToolSpec.self,
2731
LexCompilerSpec.self,
2832
YaccCompilerSpec.self,
33+
TestEntryPointGenerationToolSpec.self,
2934
]
3035
}
3136

@@ -44,3 +49,35 @@ struct UniversalPlatformSpecsExtension: SpecificationsExtension {
4449
findResourceBundle(nameWhenInstalledInToolchain: "SwiftBuild_SWBUniversalPlatform", resourceSearchPaths: resourceSearchPaths, defaultBundle: Bundle.module)?.resourceURL.map { [$0] } ?? []
4550
}
4651
}
52+
53+
struct UniversalPlatformTaskProducerExtension: TaskProducerExtension {
54+
func createPreSetupTaskProducers(_ context: SWBTaskConstruction.TaskProducerContext) -> [any SWBTaskConstruction.TaskProducer] {
55+
[]
56+
}
57+
58+
struct TestEntryPointTaskProducerFactory: TaskProducerFactory {
59+
var name: String {
60+
"TestEntryPointTaskProducerFactory"
61+
}
62+
63+
func createTaskProducer(_ context: SWBTaskConstruction.TargetTaskProducerContext, startPhaseNodes: [SWBCore.PlannedVirtualNode], endPhaseNode: SWBCore.PlannedVirtualNode) -> any SWBTaskConstruction.TaskProducer {
64+
TestEntryPointTaskProducer(context, phaseStartNodes: startPhaseNodes, phaseEndNode: endPhaseNode)
65+
}
66+
}
67+
68+
var setupTaskProducers: [any SWBTaskConstruction.TaskProducerFactory] {
69+
[TestEntryPointTaskProducerFactory()]
70+
}
71+
72+
var unorderedPostSetupTaskProducers: [any SWBTaskConstruction.TaskProducerFactory] { [] }
73+
74+
var unorderedPostBuildPhasesTaskProducers: [any SWBTaskConstruction.TaskProducerFactory] { [] }
75+
76+
var globalTaskProducers: [any SWBTaskConstruction.GlobalTaskProducerFactory] { [] }
77+
}
78+
79+
struct UniversalPlatformTaskActionExtension: TaskActionExtension {
80+
var taskActionImplementations: [SWBUtil.SerializableTypeCode : any SWBUtil.PolymorphicSerializable.Type] {
81+
[41: TestEntryPointGenerationTaskAction.self]
82+
}
83+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
(
14+
{ Identifier = "org.swift.test-entry-point-generator";
15+
Type = Compiler;
16+
Name = "Generate Test Entry Point";
17+
Description = "Generates the entry point for a test target.";
18+
CommandLine = "builtin-generateTestEntryPoint [options] --output $(OutputPath)";
19+
RuleName = "GenerateTestEntryPoint $(OutputPath)";
20+
ExecDescription = "Generate entry point for $(PRODUCT_NAME)";
21+
Outputs = (
22+
"$(OutputPath)"
23+
);
24+
Options = (
25+
);
26+
}
27+
)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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 SWBUtil
14+
import SWBCore
15+
import SWBTaskExecution
16+
import ArgumentParser
17+
18+
class TestEntryPointGenerationTaskAction: TaskAction {
19+
override class var toolIdentifier: String {
20+
"TestEntryPointGenerationTaskAction"
21+
}
22+
23+
override func performTaskAction(_ task: any ExecutableTask, dynamicExecutionDelegate: any DynamicTaskExecutionDelegate, executionDelegate: any TaskExecutionDelegate, clientDelegate: any TaskExecutionClientDelegate, outputDelegate: any TaskOutputDelegate) async -> CommandResult {
24+
do {
25+
let options = try Options.parse(Array(task.commandLineAsStrings.dropFirst()))
26+
try executionDelegate.fs.write(options.output, contents: #"""
27+
#if canImport(Testing)
28+
import Testing
29+
#endif
30+
31+
@main
32+
@available(macOS 10.15, iOS 11, watchOS 4, tvOS 11, visionOS 1, *)
33+
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
34+
struct Runner {
35+
private static func testingLibrary() -> String {
36+
var iterator = CommandLine.arguments.makeIterator()
37+
while let argument = iterator.next() {
38+
if argument == "--testing-library", let libraryName = iterator.next() {
39+
return libraryName.lowercased()
40+
}
41+
}
42+
43+
// Fallback if not specified: run XCTest (legacy behavior)
44+
return "xctest"
45+
}
46+
47+
#if os(Linux)
48+
@_silgen_name("$ss13_runAsyncMainyyyyYaKcF")
49+
private static func _runAsyncMain(_ asyncFun: @Sendable @escaping () async throws -> ())
50+
51+
static func main() {
52+
let testingLibrary = Self.testingLibrary()
53+
#if canImport(Testing)
54+
if testingLibrary == "swift-testing" {
55+
_runAsyncMain {
56+
await Testing.__swiftPMEntryPoint() as Never
57+
}
58+
}
59+
#endif
60+
}
61+
#else
62+
static func main() async {
63+
let testingLibrary = Self.testingLibrary()
64+
#if canImport(Testing)
65+
if testingLibrary == "swift-testing" {
66+
await Testing.__swiftPMEntryPoint() as Never
67+
}
68+
#endif
69+
}
70+
#endif
71+
}
72+
"""#)
73+
return .succeeded
74+
} catch {
75+
outputDelegate.emitError("\(error)")
76+
return .failed
77+
}
78+
}
79+
}
80+
81+
private struct Options: ParsableArguments {
82+
@Option var output: Path
83+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 SWBUtil
14+
import SWBMacro
15+
import SWBCore
16+
17+
final class TestEntryPointGenerationToolSpec: GenericCommandLineToolSpec, SpecIdentifierType, @unchecked Sendable {
18+
static let identifier = "org.swift.test-entry-point-generator"
19+
20+
override func createTaskAction(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) -> (any PlannedTaskAction)? {
21+
TestEntryPointGenerationTaskAction()
22+
}
23+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 SWBCore
14+
import SWBTaskConstruction
15+
16+
class TestEntryPointTaskProducer: PhasedTaskProducer, TaskProducer {
17+
func generateTasks() async -> [any PlannedTask] {
18+
var tasks: [any PlannedTask] = []
19+
if context.settings.globalScope.evaluate(BuiltinMacros.GENERATE_TEST_ENTRY_POINT) {
20+
await self.appendGeneratedTasks(&tasks) { delegate in
21+
let scope = context.settings.globalScope
22+
let outputPath = scope.evaluate(BuiltinMacros.GENERATED_TEST_ENTRY_POINT_PATH)
23+
let cbc = CommandBuildContext(producer: context, scope: scope, inputs: [], outputs: [outputPath])
24+
await context.testEntryPointGenerationToolSpec.constructTasks(cbc, delegate)
25+
}
26+
}
27+
return tasks
28+
}
29+
}
30+
31+
extension TaskProducerContext {
32+
var testEntryPointGenerationToolSpec: TestEntryPointGenerationToolSpec {
33+
return workspaceContext.core.specRegistry.getSpec(TestEntryPointGenerationToolSpec.identifier, domain: domain) as! TestEntryPointGenerationToolSpec
34+
}
35+
}

0 commit comments

Comments
 (0)