Skip to content

Commit b20fe8d

Browse files
committed
XCTest discovery support for non-Darwin platforms
Add a new target type for SwiftPM test runner executables used on Linux/Windows. XCTest discovery generates an entry point based on index store data. The index store implementation is a lightly edited version of what's in TSC.
1 parent ac358da commit b20fe8d

File tree

20 files changed

+1412
-88
lines changed

20 files changed

+1412
-88
lines changed

Sources/SWBCSupport/IndexStore.h

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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+
#ifndef INDEXSTORE_H
14+
#define INDEXSTORE_H
15+
16+
#include <stdbool.h>
17+
#include <stddef.h>
18+
#include <stdint.h>
19+
#include <time.h>
20+
21+
typedef void *indexstore_error_t;
22+
23+
typedef struct {
24+
const char *data;
25+
size_t length;
26+
} indexstore_string_ref_t;
27+
28+
typedef void *indexstore_t;
29+
typedef void *indexstore_symbol_t;
30+
31+
typedef enum {
32+
INDEXSTORE_SYMBOL_KIND_UNKNOWN = 0,
33+
INDEXSTORE_SYMBOL_KIND_MODULE = 1,
34+
INDEXSTORE_SYMBOL_KIND_NAMESPACE = 2,
35+
INDEXSTORE_SYMBOL_KIND_NAMESPACEALIAS = 3,
36+
INDEXSTORE_SYMBOL_KIND_MACRO = 4,
37+
INDEXSTORE_SYMBOL_KIND_ENUM = 5,
38+
INDEXSTORE_SYMBOL_KIND_STRUCT = 6,
39+
INDEXSTORE_SYMBOL_KIND_CLASS = 7,
40+
INDEXSTORE_SYMBOL_KIND_PROTOCOL = 8,
41+
INDEXSTORE_SYMBOL_KIND_EXTENSION = 9,
42+
INDEXSTORE_SYMBOL_KIND_UNION = 10,
43+
INDEXSTORE_SYMBOL_KIND_TYPEALIAS = 11,
44+
INDEXSTORE_SYMBOL_KIND_FUNCTION = 12,
45+
INDEXSTORE_SYMBOL_KIND_VARIABLE = 13,
46+
INDEXSTORE_SYMBOL_KIND_FIELD = 14,
47+
INDEXSTORE_SYMBOL_KIND_ENUMCONSTANT = 15,
48+
INDEXSTORE_SYMBOL_KIND_INSTANCEMETHOD = 16,
49+
INDEXSTORE_SYMBOL_KIND_CLASSMETHOD = 17,
50+
INDEXSTORE_SYMBOL_KIND_STATICMETHOD = 18,
51+
INDEXSTORE_SYMBOL_KIND_INSTANCEPROPERTY = 19,
52+
INDEXSTORE_SYMBOL_KIND_CLASSPROPERTY = 20,
53+
INDEXSTORE_SYMBOL_KIND_STATICPROPERTY = 21,
54+
INDEXSTORE_SYMBOL_KIND_CONSTRUCTOR = 22,
55+
INDEXSTORE_SYMBOL_KIND_DESTRUCTOR = 23,
56+
INDEXSTORE_SYMBOL_KIND_CONVERSIONFUNCTION = 24,
57+
INDEXSTORE_SYMBOL_KIND_PARAMETER = 25,
58+
INDEXSTORE_SYMBOL_KIND_USING = 26,
59+
60+
INDEXSTORE_SYMBOL_KIND_COMMENTTAG = 1000,
61+
} indexstore_symbol_kind_t;
62+
63+
typedef enum {
64+
INDEXSTORE_SYMBOL_PROPERTY_GENERIC = 1 << 0,
65+
INDEXSTORE_SYMBOL_PROPERTY_TEMPLATE_PARTIAL_SPECIALIZATION = 1 << 1,
66+
INDEXSTORE_SYMBOL_PROPERTY_TEMPLATE_SPECIALIZATION = 1 << 2,
67+
INDEXSTORE_SYMBOL_PROPERTY_UNITTEST = 1 << 3,
68+
INDEXSTORE_SYMBOL_PROPERTY_IBANNOTATED = 1 << 4,
69+
INDEXSTORE_SYMBOL_PROPERTY_IBOUTLETCOLLECTION = 1 << 5,
70+
INDEXSTORE_SYMBOL_PROPERTY_GKINSPECTABLE = 1 << 6,
71+
INDEXSTORE_SYMBOL_PROPERTY_LOCAL = 1 << 7,
72+
INDEXSTORE_SYMBOL_PROPERTY_PROTOCOL_INTERFACE = 1 << 8,
73+
INDEXSTORE_SYMBOL_PROPERTY_SWIFT_ASYNC = 1 << 16,
74+
} indexstore_symbol_property_t;
75+
76+
typedef enum {
77+
INDEXSTORE_SYMBOL_ROLE_DECLARATION = 1 << 0,
78+
INDEXSTORE_SYMBOL_ROLE_DEFINITION = 1 << 1,
79+
INDEXSTORE_SYMBOL_ROLE_REFERENCE = 1 << 2,
80+
INDEXSTORE_SYMBOL_ROLE_READ = 1 << 3,
81+
INDEXSTORE_SYMBOL_ROLE_WRITE = 1 << 4,
82+
INDEXSTORE_SYMBOL_ROLE_CALL = 1 << 5,
83+
INDEXSTORE_SYMBOL_ROLE_DYNAMIC = 1 << 6,
84+
INDEXSTORE_SYMBOL_ROLE_ADDRESSOF = 1 << 7,
85+
INDEXSTORE_SYMBOL_ROLE_IMPLICIT = 1 << 8,
86+
INDEXSTORE_SYMBOL_ROLE_UNDEFINITION = 1 << 19,
87+
88+
// Relation roles.
89+
INDEXSTORE_SYMBOL_ROLE_REL_CHILDOF = 1 << 9,
90+
INDEXSTORE_SYMBOL_ROLE_REL_BASEOF = 1 << 10,
91+
INDEXSTORE_SYMBOL_ROLE_REL_OVERRIDEOF = 1 << 11,
92+
INDEXSTORE_SYMBOL_ROLE_REL_RECEIVEDBY = 1 << 12,
93+
INDEXSTORE_SYMBOL_ROLE_REL_CALLEDBY = 1 << 13,
94+
INDEXSTORE_SYMBOL_ROLE_REL_EXTENDEDBY = 1 << 14,
95+
INDEXSTORE_SYMBOL_ROLE_REL_ACCESSOROF = 1 << 15,
96+
INDEXSTORE_SYMBOL_ROLE_REL_CONTAINEDBY = 1 << 16,
97+
INDEXSTORE_SYMBOL_ROLE_REL_IBTYPEOF = 1 << 17,
98+
INDEXSTORE_SYMBOL_ROLE_REL_SPECIALIZATIONOF = 1 << 18,
99+
} indexstore_symbol_role_t;
100+
101+
typedef void *indexstore_unit_dependency_t;
102+
103+
typedef enum {
104+
INDEXSTORE_UNIT_DEPENDENCY_UNIT = 1,
105+
INDEXSTORE_UNIT_DEPENDENCY_RECORD = 2,
106+
INDEXSTORE_UNIT_DEPENDENCY_FILE = 3,
107+
} indexstore_unit_dependency_kind_t;
108+
109+
typedef void *indexstore_symbol_relation_t;
110+
typedef void *indexstore_occurrence_t;
111+
typedef void *indexstore_record_reader_t;
112+
typedef void *indexstore_unit_reader_t;
113+
114+
typedef struct {
115+
const char *
116+
(*error_get_description)(indexstore_error_t);
117+
118+
void
119+
(*error_dispose)(indexstore_error_t);
120+
121+
indexstore_t
122+
(*store_create)(const char *store_path, indexstore_error_t *error);
123+
124+
void
125+
(*store_dispose)(indexstore_t);
126+
127+
size_t
128+
(*store_get_unit_name_from_output_path)(indexstore_t store,
129+
const char *output_path,
130+
char *name_buf,
131+
size_t buf_size);
132+
133+
indexstore_symbol_kind_t
134+
(*symbol_get_kind)(indexstore_symbol_t);
135+
136+
uint64_t
137+
(*symbol_get_properties)(indexstore_symbol_t);
138+
139+
indexstore_string_ref_t
140+
(*symbol_get_name)(indexstore_symbol_t);
141+
142+
uint64_t
143+
(*symbol_relation_get_roles)(indexstore_symbol_relation_t);
144+
145+
indexstore_symbol_t
146+
(*symbol_relation_get_symbol)(indexstore_symbol_relation_t);
147+
148+
indexstore_symbol_t
149+
(*occurrence_get_symbol)(indexstore_occurrence_t);
150+
151+
bool
152+
(*occurrence_relations_apply_f)(indexstore_occurrence_t,
153+
void *context,
154+
bool(*applier)(void *context, indexstore_symbol_relation_t symbol_rel));
155+
156+
indexstore_record_reader_t
157+
(*record_reader_create)(indexstore_t store, const char *record_name,
158+
indexstore_error_t *error);
159+
160+
void
161+
(*record_reader_dispose)(indexstore_record_reader_t);
162+
163+
bool
164+
(*record_reader_occurrences_apply_f)(indexstore_record_reader_t,
165+
void *context,
166+
bool(*applier)(void *context, indexstore_occurrence_t occur));
167+
168+
indexstore_unit_reader_t
169+
(*unit_reader_create)(indexstore_t store, const char *unit_name,
170+
indexstore_error_t *error);
171+
172+
void
173+
(*unit_reader_dispose)(indexstore_unit_reader_t);
174+
175+
indexstore_string_ref_t
176+
(*unit_reader_get_module_name)(indexstore_unit_reader_t);
177+
178+
indexstore_unit_dependency_kind_t
179+
(*unit_dependency_get_kind)(indexstore_unit_dependency_t);
180+
181+
indexstore_string_ref_t
182+
(*unit_dependency_get_name)(indexstore_unit_dependency_t);
183+
184+
bool
185+
(*unit_reader_dependencies_apply)(indexstore_unit_reader_t,
186+
bool(^applier)(indexstore_unit_dependency_t));
187+
188+
bool
189+
(*unit_reader_dependencies_apply_f)(indexstore_unit_reader_t,
190+
void *context,
191+
bool(*applier)(void *context, indexstore_unit_dependency_t));
192+
} indexstore_functions_t;
193+
194+
#endif

Sources/SWBCSupport/SWBCSupport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include "CLibclang.h"
2323
#include "CLibRemarksHelper.h"
24+
#include "IndexStore.h"
2425
#include "PluginAPI.h"
2526
#include "PluginAPI_functions.h"
2627
#include "PluginAPI_types.h"

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,7 @@ public final class BuiltinMacros {
750750
public static let INDEX_PREPARED_TARGET_MARKER_PATH = BuiltinMacros.declareStringMacro("INDEX_PREPARED_TARGET_MARKER_PATH")
751751
public static let INDEX_REGULAR_BUILD_PRODUCTS_DIR = BuiltinMacros.declareStringMacro("INDEX_REGULAR_BUILD_PRODUCTS_DIR")
752752
public static let INDEX_REGULAR_BUILD_INTERMEDIATES_DIR = BuiltinMacros.declareStringMacro("INDEX_REGULAR_BUILD_INTERMEDIATES_DIR")
753+
public static let INDEX_STORE_LIBRARY_PATH = BuiltinMacros.declarePathMacro("INDEX_STORE_LIBRARY_PATH")
753754
public static let INFOPLIST_ENFORCE_MINIMUM_OS = BuiltinMacros.declareBooleanMacro("INFOPLIST_ENFORCE_MINIMUM_OS")
754755
public static let INFOPLIST_EXPAND_BUILD_SETTINGS = BuiltinMacros.declareBooleanMacro("INFOPLIST_EXPAND_BUILD_SETTINGS")
755756
public static let INFOPLIST_FILE = BuiltinMacros.declarePathMacro("INFOPLIST_FILE")
@@ -1790,6 +1791,7 @@ public final class BuiltinMacros {
17901791
INDEX_PREPARED_TARGET_MARKER_PATH,
17911792
INDEX_REGULAR_BUILD_PRODUCTS_DIR,
17921793
INDEX_REGULAR_BUILD_INTERMEDIATES_DIR,
1794+
INDEX_STORE_LIBRARY_PATH,
17931795
INDEX_ENABLE_DATA_STORE,
17941796
INDEX_PRECOMPS_DIR,
17951797
INFOPLIST_ENFORCE_MINIMUM_OS,

Sources/SWBCore/SpecImplementations/ProductTypes.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ public class ProductTypeSpec : Spec, SpecType, @unchecked Sendable {
321321
}
322322

323323
/// Returns whether the product type supports embedding Swift standard libraries inside it.
324-
public var supportsEmbeddingSwiftStandardLibraries: Bool {
324+
public func supportsEmbeddingSwiftStandardLibraries(producer: CommandProducer) -> Bool {
325325
// Most product types don't support having the Swift libraries embedded in them.
326326
return false
327327
}
@@ -381,7 +381,7 @@ public final class ApplicationProductTypeSpec : BundleProductTypeSpec, @unchecke
381381
return "PBXApplicationProductType"
382382
}
383383

384-
public override var supportsEmbeddingSwiftStandardLibraries: Bool {
384+
public override func supportsEmbeddingSwiftStandardLibraries(producer: CommandProducer) -> Bool {
385385
return true
386386
}
387387

@@ -602,8 +602,8 @@ public final class XCTestBundleProductTypeSpec : BundleProductTypeSpec, @uncheck
602602
super.init(parser, basedOnSpec)
603603
}
604604

605-
public override var supportsEmbeddingSwiftStandardLibraries: Bool {
606-
return true
605+
public override func supportsEmbeddingSwiftStandardLibraries(producer: CommandProducer) -> Bool {
606+
return producer.isApplePlatform
607607
}
608608

609609
public class func usesXCTRunner(_ scope: MacroEvaluationScope) -> Bool {
@@ -649,7 +649,7 @@ public final class XCTestBundleProductTypeSpec : BundleProductTypeSpec, @uncheck
649649
var (tableOpt, warnings, errors) = super.overridingBuildSettings(scope, platform: platform)
650650
var table = tableOpt ?? MacroValueAssignmentTable(namespace: scope.namespace)
651651

652-
let isDeviceBuild = platform?.isDeploymentPlatform == true && platform?.identifier != "com.apple.platform.macosx"
652+
let isDeviceBuild = platform?.isDeploymentPlatform == true && platform?.name != scope.evaluate(BuiltinMacros.HOST_PLATFORM)
653653
if isDeviceBuild {
654654
// For tests running on devices (not simulators) we always want to generate dSYMs so that symbolication can give file and line information about test failures.
655655
table.push(BuiltinMacros.DEBUG_INFORMATION_FORMAT, literal: "dwarf-with-dsym")

Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3766,6 +3766,9 @@ public extension BuildPhaseWithBuildFiles {
37663766
/// - Returns: If the build phase contains any Swift source files that are not filtered out via the platform filter or excluded source file name patterns.
37673767
func containsSwiftSources(_ referenceLookupContext: any ReferenceLookupContext, _ specLookupContext: any SpecLookupContext, _ scope: MacroEvaluationScope, _ filePathResolver: FilePathResolver) -> Bool {
37683768
guard let swiftFileType = specLookupContext.lookupFileType(identifier: "sourcecode.swift") else { return false }
3769+
if scope.evaluate(BuiltinMacros.GENERATE_TEST_ENTRY_POINT) {
3770+
return true
3771+
}
37693772
return containsFiles(ofType: swiftFileType, referenceLookupContext, specLookupContext, scope, filePathResolver)
37703773
}
37713774
}

Sources/SWBGenericUnixPlatform/Specs/Unix.xcspec

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,41 +24,18 @@
2424
SortNumber = 0;
2525
},
2626

27-
// Test type bundle (bodged to be a tool)
2827
{
2928
Domain = generic-unix;
3029
Type = ProductType;
3130
Identifier = com.apple.product-type.bundle.unit-test;
32-
Class = PBXToolProductType;
33-
Name = "Command-line Tool";
34-
Description = "Standalone command-line tool";
35-
DefaultTargetName = "Command-line Tool";
31+
BasedOn = com.apple.product-type.library.dynamic;
3632
DefaultBuildProperties = {
37-
FULL_PRODUCT_NAME = "$(EXECUTABLE_NAME)";
38-
EXECUTABLE_PREFIX = "";
39-
EXECUTABLE_SUFFIX = ".xctest";
40-
REZ_EXECUTABLE = YES;
41-
INSTALL_PATH = "/usr/local/bin";
42-
FRAMEWORK_FLAG_PREFIX = "-framework";
43-
LIBRARY_FLAG_PREFIX = "-l";
44-
LIBRARY_FLAG_NOSPACE = YES;
45-
GCC_DYNAMIC_NO_PIC = NO;
46-
LD_NO_PIE = NO;
47-
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
48-
GCC_INLINES_ARE_PRIVATE_EXTERN = YES;
49-
STRIP_STYLE = "all";
50-
CODE_SIGNING_ALLOWED = NO;
51-
IsUnitTest = YES;
52-
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = YES;
53-
SWIFT_FORCE_STATIC_LINK_STDLIB = NO;
54-
// Avoid warning for executable types
55-
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";
33+
// Index store data is required to discover XCTest tests
34+
COMPILER_INDEX_STORE_ENABLE = YES;
35+
SWIFT_INDEX_STORE_ENABLE = YES;
36+
// Testability is needed to generate code to invoke discovered XCTest tests
37+
SWIFT_ENABLE_TESTABILITY = YES;
5838
};
59-
PackageTypes = (
60-
com.apple.package-type.mach-o-executable // default
61-
);
6239
},
6340

6441
// Dynamic library (masquerading as a framework to placate Swift's project structure)

Sources/SWBProjectModel/PIFGenerationModel.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ public enum PIF {
295295
case executable = "com.apple.product-type.tool"
296296
case hostBuildTool = "com.apple.product-type.tool.host-build"
297297
case unitTest = "com.apple.product-type.bundle.unit-test"
298+
case swiftpmTestRunner = "com.apple.product-type.tool.swiftpm-test-runner"
298299
case bundle = "com.apple.product-type.bundle"
299300
case packageProduct = "packageProduct"
300301
public var asString: String { return rawValue }

Sources/SWBQNXPlatform/Specs/QNX.xcspec

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,39 +24,18 @@
2424
SortNumber = 0;
2525
},
2626

27-
// Test type bundle (bodged to be a tool)
2827
{
2928
Domain = qnx;
3029
Type = ProductType;
3130
Identifier = com.apple.product-type.bundle.unit-test;
32-
Class = PBXToolProductType;
33-
Name = "Command-line Tool";
34-
Description = "Standalone command-line tool";
35-
DefaultTargetName = "Command-line Tool";
31+
BasedOn = com.apple.product-type.library.dynamic;
3632
DefaultBuildProperties = {
37-
FULL_PRODUCT_NAME = "$(EXECUTABLE_NAME)";
38-
EXECUTABLE_PREFIX = "";
39-
EXECUTABLE_SUFFIX = ".xctest";
40-
REZ_EXECUTABLE = YES;
41-
INSTALL_PATH = "/usr/local/bin";
42-
FRAMEWORK_FLAG_PREFIX = "-framework";
43-
LIBRARY_FLAG_PREFIX = "-l";
44-
LIBRARY_FLAG_NOSPACE = YES;
45-
GCC_DYNAMIC_NO_PIC = NO;
46-
LD_NO_PIE = NO;
47-
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
48-
GCC_INLINES_ARE_PRIVATE_EXTERN = YES;
49-
STRIP_STYLE = "all";
50-
CODE_SIGNING_ALLOWED = NO;
51-
IsUnitTest = YES;
52-
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = YES;
53-
SWIFT_FORCE_STATIC_LINK_STDLIB = NO;
54-
// Avoid warning for executable types
55-
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
33+
// Index store data is required to discover XCTest tests
34+
COMPILER_INDEX_STORE_ENABLE = YES;
35+
SWIFT_INDEX_STORE_ENABLE = YES;
36+
// Testability is needed to generate code to invoke discovered XCTest tests
37+
SWIFT_ENABLE_TESTABILITY = YES;
5638
};
57-
PackageTypes = (
58-
com.apple.package-type.mach-o-executable // default
59-
);
6039
},
6140

6241
// Dynamic library (masquerading as a framework to placate Swift's project structure)

Sources/SWBTaskConstruction/ProductPlanning/ProductPlan.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ package protocol GlobalProductPlanDelegate: CoreClientTargetDiagnosticProducingD
3131
package final class GlobalProductPlan: GlobalTargetInfoProvider
3232
{
3333
/// The build plan request.
34-
let planRequest: BuildPlanRequest
34+
package let planRequest: BuildPlanRequest
3535

3636
/// The target task info for each configured target.
3737
private(set) var targetTaskInfos: [ConfiguredTarget: TargetTaskInfo]

Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/SwiftStandardLibrariesTaskProducer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ final class SwiftStandardLibrariesTaskProducer: PhasedTaskProducer, TaskProducer
4141
let buildingAnySwiftSourceFiles = (context.configuredTarget?.target as? BuildPhaseTarget)?.sourcesBuildPhase?.containsSwiftSources(context.workspaceContext.workspace, context, scope, context.filePathResolver) ?? false
4242

4343
// Determine whether we want to embed swift libraries.
44-
var shouldEmbedSwiftLibraries = (buildingAnySwiftSourceFiles && productType.supportsEmbeddingSwiftStandardLibraries)
44+
var shouldEmbedSwiftLibraries = (buildingAnySwiftSourceFiles && productType.supportsEmbeddingSwiftStandardLibraries(producer: context))
4545
// If ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES then we will override our earlier reasoning if the product is a wrapper.
4646
if !shouldEmbedSwiftLibraries && scope.evaluate(BuiltinMacros.ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES)
4747
{

0 commit comments

Comments
 (0)