Skip to content

XCTest discovery support for non-Darwin platforms #499

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions Sources/SWBCSupport/IndexStore.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#ifndef INDEXSTORE_H
#define INDEXSTORE_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>

typedef void *indexstore_error_t;

typedef struct {
const char *data;
size_t length;
} indexstore_string_ref_t;

typedef void *indexstore_t;
typedef void *indexstore_symbol_t;

typedef enum {
INDEXSTORE_SYMBOL_KIND_UNKNOWN = 0,
INDEXSTORE_SYMBOL_KIND_MODULE = 1,
INDEXSTORE_SYMBOL_KIND_NAMESPACE = 2,
INDEXSTORE_SYMBOL_KIND_NAMESPACEALIAS = 3,
INDEXSTORE_SYMBOL_KIND_MACRO = 4,
INDEXSTORE_SYMBOL_KIND_ENUM = 5,
INDEXSTORE_SYMBOL_KIND_STRUCT = 6,
INDEXSTORE_SYMBOL_KIND_CLASS = 7,
INDEXSTORE_SYMBOL_KIND_PROTOCOL = 8,
INDEXSTORE_SYMBOL_KIND_EXTENSION = 9,
INDEXSTORE_SYMBOL_KIND_UNION = 10,
INDEXSTORE_SYMBOL_KIND_TYPEALIAS = 11,
INDEXSTORE_SYMBOL_KIND_FUNCTION = 12,
INDEXSTORE_SYMBOL_KIND_VARIABLE = 13,
INDEXSTORE_SYMBOL_KIND_FIELD = 14,
INDEXSTORE_SYMBOL_KIND_ENUMCONSTANT = 15,
INDEXSTORE_SYMBOL_KIND_INSTANCEMETHOD = 16,
INDEXSTORE_SYMBOL_KIND_CLASSMETHOD = 17,
INDEXSTORE_SYMBOL_KIND_STATICMETHOD = 18,
INDEXSTORE_SYMBOL_KIND_INSTANCEPROPERTY = 19,
INDEXSTORE_SYMBOL_KIND_CLASSPROPERTY = 20,
INDEXSTORE_SYMBOL_KIND_STATICPROPERTY = 21,
INDEXSTORE_SYMBOL_KIND_CONSTRUCTOR = 22,
INDEXSTORE_SYMBOL_KIND_DESTRUCTOR = 23,
INDEXSTORE_SYMBOL_KIND_CONVERSIONFUNCTION = 24,
INDEXSTORE_SYMBOL_KIND_PARAMETER = 25,
INDEXSTORE_SYMBOL_KIND_USING = 26,

INDEXSTORE_SYMBOL_KIND_COMMENTTAG = 1000,
} indexstore_symbol_kind_t;

typedef enum {
INDEXSTORE_SYMBOL_PROPERTY_GENERIC = 1 << 0,
INDEXSTORE_SYMBOL_PROPERTY_TEMPLATE_PARTIAL_SPECIALIZATION = 1 << 1,
INDEXSTORE_SYMBOL_PROPERTY_TEMPLATE_SPECIALIZATION = 1 << 2,
INDEXSTORE_SYMBOL_PROPERTY_UNITTEST = 1 << 3,
INDEXSTORE_SYMBOL_PROPERTY_IBANNOTATED = 1 << 4,
INDEXSTORE_SYMBOL_PROPERTY_IBOUTLETCOLLECTION = 1 << 5,
INDEXSTORE_SYMBOL_PROPERTY_GKINSPECTABLE = 1 << 6,
INDEXSTORE_SYMBOL_PROPERTY_LOCAL = 1 << 7,
INDEXSTORE_SYMBOL_PROPERTY_PROTOCOL_INTERFACE = 1 << 8,
INDEXSTORE_SYMBOL_PROPERTY_SWIFT_ASYNC = 1 << 16,
} indexstore_symbol_property_t;

typedef enum {
INDEXSTORE_SYMBOL_ROLE_DECLARATION = 1 << 0,
INDEXSTORE_SYMBOL_ROLE_DEFINITION = 1 << 1,
INDEXSTORE_SYMBOL_ROLE_REFERENCE = 1 << 2,
INDEXSTORE_SYMBOL_ROLE_READ = 1 << 3,
INDEXSTORE_SYMBOL_ROLE_WRITE = 1 << 4,
INDEXSTORE_SYMBOL_ROLE_CALL = 1 << 5,
INDEXSTORE_SYMBOL_ROLE_DYNAMIC = 1 << 6,
INDEXSTORE_SYMBOL_ROLE_ADDRESSOF = 1 << 7,
INDEXSTORE_SYMBOL_ROLE_IMPLICIT = 1 << 8,
INDEXSTORE_SYMBOL_ROLE_UNDEFINITION = 1 << 19,

// Relation roles.
INDEXSTORE_SYMBOL_ROLE_REL_CHILDOF = 1 << 9,
INDEXSTORE_SYMBOL_ROLE_REL_BASEOF = 1 << 10,
INDEXSTORE_SYMBOL_ROLE_REL_OVERRIDEOF = 1 << 11,
INDEXSTORE_SYMBOL_ROLE_REL_RECEIVEDBY = 1 << 12,
INDEXSTORE_SYMBOL_ROLE_REL_CALLEDBY = 1 << 13,
INDEXSTORE_SYMBOL_ROLE_REL_EXTENDEDBY = 1 << 14,
INDEXSTORE_SYMBOL_ROLE_REL_ACCESSOROF = 1 << 15,
INDEXSTORE_SYMBOL_ROLE_REL_CONTAINEDBY = 1 << 16,
INDEXSTORE_SYMBOL_ROLE_REL_IBTYPEOF = 1 << 17,
INDEXSTORE_SYMBOL_ROLE_REL_SPECIALIZATIONOF = 1 << 18,
} indexstore_symbol_role_t;

typedef void *indexstore_unit_dependency_t;

typedef enum {
INDEXSTORE_UNIT_DEPENDENCY_UNIT = 1,
INDEXSTORE_UNIT_DEPENDENCY_RECORD = 2,
INDEXSTORE_UNIT_DEPENDENCY_FILE = 3,
} indexstore_unit_dependency_kind_t;

typedef void *indexstore_symbol_relation_t;
typedef void *indexstore_occurrence_t;
typedef void *indexstore_record_reader_t;
typedef void *indexstore_unit_reader_t;

typedef struct {
const char *
(*error_get_description)(indexstore_error_t);

void
(*error_dispose)(indexstore_error_t);

indexstore_t
(*store_create)(const char *store_path, indexstore_error_t *error);

void
(*store_dispose)(indexstore_t);

size_t
(*store_get_unit_name_from_output_path)(indexstore_t store,
const char *output_path,
char *name_buf,
size_t buf_size);

indexstore_symbol_kind_t
(*symbol_get_kind)(indexstore_symbol_t);

uint64_t
(*symbol_get_properties)(indexstore_symbol_t);

indexstore_string_ref_t
(*symbol_get_name)(indexstore_symbol_t);

uint64_t
(*symbol_relation_get_roles)(indexstore_symbol_relation_t);

indexstore_symbol_t
(*symbol_relation_get_symbol)(indexstore_symbol_relation_t);

indexstore_symbol_t
(*occurrence_get_symbol)(indexstore_occurrence_t);

bool
(*occurrence_relations_apply_f)(indexstore_occurrence_t,
void *context,
bool(*applier)(void *context, indexstore_symbol_relation_t symbol_rel));

indexstore_record_reader_t
(*record_reader_create)(indexstore_t store, const char *record_name,
indexstore_error_t *error);

void
(*record_reader_dispose)(indexstore_record_reader_t);

bool
(*record_reader_occurrences_apply_f)(indexstore_record_reader_t,
void *context,
bool(*applier)(void *context, indexstore_occurrence_t occur));

indexstore_unit_reader_t
(*unit_reader_create)(indexstore_t store, const char *unit_name,
indexstore_error_t *error);

void
(*unit_reader_dispose)(indexstore_unit_reader_t);

indexstore_string_ref_t
(*unit_reader_get_module_name)(indexstore_unit_reader_t);

indexstore_unit_dependency_kind_t
(*unit_dependency_get_kind)(indexstore_unit_dependency_t);

indexstore_string_ref_t
(*unit_dependency_get_name)(indexstore_unit_dependency_t);

bool
(*unit_reader_dependencies_apply)(indexstore_unit_reader_t,
bool(^applier)(indexstore_unit_dependency_t));

bool
(*unit_reader_dependencies_apply_f)(indexstore_unit_reader_t,
void *context,
bool(*applier)(void *context, indexstore_unit_dependency_t));
} indexstore_functions_t;

#endif
1 change: 1 addition & 0 deletions Sources/SWBCSupport/SWBCSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "CLibclang.h"
#include "CLibRemarksHelper.h"
#include "IndexStore.h"
#include "PluginAPI.h"
#include "PluginAPI_functions.h"
#include "PluginAPI_types.h"
Expand Down
2 changes: 2 additions & 0 deletions Sources/SWBCore/Settings/BuiltinMacros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,7 @@ public final class BuiltinMacros {
public static let INDEX_PREPARED_TARGET_MARKER_PATH = BuiltinMacros.declareStringMacro("INDEX_PREPARED_TARGET_MARKER_PATH")
public static let INDEX_REGULAR_BUILD_PRODUCTS_DIR = BuiltinMacros.declareStringMacro("INDEX_REGULAR_BUILD_PRODUCTS_DIR")
public static let INDEX_REGULAR_BUILD_INTERMEDIATES_DIR = BuiltinMacros.declareStringMacro("INDEX_REGULAR_BUILD_INTERMEDIATES_DIR")
public static let INDEX_STORE_LIBRARY_PATH = BuiltinMacros.declarePathMacro("INDEX_STORE_LIBRARY_PATH")
public static let INFOPLIST_ENFORCE_MINIMUM_OS = BuiltinMacros.declareBooleanMacro("INFOPLIST_ENFORCE_MINIMUM_OS")
public static let INFOPLIST_EXPAND_BUILD_SETTINGS = BuiltinMacros.declareBooleanMacro("INFOPLIST_EXPAND_BUILD_SETTINGS")
public static let INFOPLIST_FILE = BuiltinMacros.declarePathMacro("INFOPLIST_FILE")
Expand Down Expand Up @@ -1790,6 +1791,7 @@ public final class BuiltinMacros {
INDEX_PREPARED_TARGET_MARKER_PATH,
INDEX_REGULAR_BUILD_PRODUCTS_DIR,
INDEX_REGULAR_BUILD_INTERMEDIATES_DIR,
INDEX_STORE_LIBRARY_PATH,
INDEX_ENABLE_DATA_STORE,
INDEX_PRECOMPS_DIR,
INFOPLIST_ENFORCE_MINIMUM_OS,
Expand Down
10 changes: 5 additions & 5 deletions Sources/SWBCore/SpecImplementations/ProductTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ public class ProductTypeSpec : Spec, SpecType, @unchecked Sendable {
}

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

public override var supportsEmbeddingSwiftStandardLibraries: Bool {
public override func supportsEmbeddingSwiftStandardLibraries(producer: CommandProducer) -> Bool {
return true
}

Expand Down Expand Up @@ -602,8 +602,8 @@ public final class XCTestBundleProductTypeSpec : BundleProductTypeSpec, @uncheck
super.init(parser, basedOnSpec)
}

public override var supportsEmbeddingSwiftStandardLibraries: Bool {
return true
public override func supportsEmbeddingSwiftStandardLibraries(producer: CommandProducer) -> Bool {
return producer.isApplePlatform
}

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

let isDeviceBuild = platform?.isDeploymentPlatform == true && platform?.identifier != "com.apple.platform.macosx"
let isDeviceBuild = platform?.isDeploymentPlatform == true && platform?.name != scope.evaluate(BuiltinMacros.HOST_PLATFORM)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an aside, this seems fundamentally flawed. There's no guarantee you're running the resulting binaries on the same host that built them, so it seems the host platform should enable this too.

Why simulators are excluded I'm not sure.

if isDeviceBuild {
// 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.
table.push(BuiltinMacros.DEBUG_INFORMATION_FORMAT, literal: "dwarf-with-dsym")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3766,6 +3766,9 @@ public extension BuildPhaseWithBuildFiles {
/// - 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.
func containsSwiftSources(_ referenceLookupContext: any ReferenceLookupContext, _ specLookupContext: any SpecLookupContext, _ scope: MacroEvaluationScope, _ filePathResolver: FilePathResolver) -> Bool {
guard let swiftFileType = specLookupContext.lookupFileType(identifier: "sourcecode.swift") else { return false }
if scope.evaluate(BuiltinMacros.GENERATE_TEST_ENTRY_POINT) {
return true
}
return containsFiles(ofType: swiftFileType, referenceLookupContext, specLookupContext, scope, filePathResolver)
}
}
Expand Down
35 changes: 6 additions & 29 deletions Sources/SWBGenericUnixPlatform/Specs/Unix.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,41 +24,18 @@
SortNumber = 0;
},

// Test type bundle (bodged to be a tool)
{
Domain = generic-unix;
Type = ProductType;
Identifier = com.apple.product-type.bundle.unit-test;
Class = PBXToolProductType;
Name = "Command-line Tool";
Description = "Standalone command-line tool";
DefaultTargetName = "Command-line Tool";
BasedOn = com.apple.product-type.library.dynamic;
DefaultBuildProperties = {
FULL_PRODUCT_NAME = "$(EXECUTABLE_NAME)";
EXECUTABLE_PREFIX = "";
EXECUTABLE_SUFFIX = ".xctest";
REZ_EXECUTABLE = YES;
INSTALL_PATH = "/usr/local/bin";
FRAMEWORK_FLAG_PREFIX = "-framework";
LIBRARY_FLAG_PREFIX = "-l";
LIBRARY_FLAG_NOSPACE = YES;
GCC_DYNAMIC_NO_PIC = NO;
LD_NO_PIE = NO;
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
GCC_INLINES_ARE_PRIVATE_EXTERN = YES;
STRIP_STYLE = "all";
CODE_SIGNING_ALLOWED = NO;
IsUnitTest = YES;
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = YES;
SWIFT_FORCE_STATIC_LINK_STDLIB = NO;
// Avoid warning for executable types
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
GENERATE_TEST_ENTRY_POINT = YES;
GENERATED_TEST_ENTRY_POINT_PATH = "$(DERIVED_SOURCES_DIR)/test_entry_point.swift";
// Index store data is required to discover XCTest tests
COMPILER_INDEX_STORE_ENABLE = YES;
SWIFT_INDEX_STORE_ENABLE = YES;
// Testability is needed to generate code to invoke discovered XCTest tests
SWIFT_ENABLE_TESTABILITY = YES;
};
PackageTypes = (
com.apple.package-type.mach-o-executable // default
);
},

// Dynamic library (masquerading as a framework to placate Swift's project structure)
Expand Down
1 change: 1 addition & 0 deletions Sources/SWBProjectModel/PIFGenerationModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ public enum PIF {
case executable = "com.apple.product-type.tool"
case hostBuildTool = "com.apple.product-type.tool.host-build"
case unitTest = "com.apple.product-type.bundle.unit-test"
case swiftpmTestRunner = "com.apple.product-type.tool.swiftpm-test-runner"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to be called "SwiftPM" test runner?

case bundle = "com.apple.product-type.bundle"
case packageProduct = "packageProduct"
public var asString: String { return rawValue }
Expand Down
33 changes: 6 additions & 27 deletions Sources/SWBQNXPlatform/Specs/QNX.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,39 +24,18 @@
SortNumber = 0;
},

// Test type bundle (bodged to be a tool)
{
Domain = qnx;
Type = ProductType;
Identifier = com.apple.product-type.bundle.unit-test;
Class = PBXToolProductType;
Name = "Command-line Tool";
Description = "Standalone command-line tool";
DefaultTargetName = "Command-line Tool";
BasedOn = com.apple.product-type.library.dynamic;
DefaultBuildProperties = {
FULL_PRODUCT_NAME = "$(EXECUTABLE_NAME)";
EXECUTABLE_PREFIX = "";
EXECUTABLE_SUFFIX = ".xctest";
REZ_EXECUTABLE = YES;
INSTALL_PATH = "/usr/local/bin";
FRAMEWORK_FLAG_PREFIX = "-framework";
LIBRARY_FLAG_PREFIX = "-l";
LIBRARY_FLAG_NOSPACE = YES;
GCC_DYNAMIC_NO_PIC = NO;
LD_NO_PIE = NO;
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
GCC_INLINES_ARE_PRIVATE_EXTERN = YES;
STRIP_STYLE = "all";
CODE_SIGNING_ALLOWED = NO;
IsUnitTest = YES;
SWIFT_FORCE_DYNAMIC_LINK_STDLIB = YES;
SWIFT_FORCE_STATIC_LINK_STDLIB = NO;
// Avoid warning for executable types
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO;
// Index store data is required to discover XCTest tests
COMPILER_INDEX_STORE_ENABLE = YES;
SWIFT_INDEX_STORE_ENABLE = YES;
// Testability is needed to generate code to invoke discovered XCTest tests
SWIFT_ENABLE_TESTABILITY = YES;
};
PackageTypes = (
com.apple.package-type.mach-o-executable // default
);
},

// Dynamic library (masquerading as a framework to placate Swift's project structure)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ package protocol GlobalProductPlanDelegate: CoreClientTargetDiagnosticProducingD
package final class GlobalProductPlan: GlobalTargetInfoProvider
{
/// The build plan request.
let planRequest: BuildPlanRequest
package let planRequest: BuildPlanRequest

/// The target task info for each configured target.
private(set) var targetTaskInfos: [ConfiguredTarget: TargetTaskInfo]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class SwiftStandardLibrariesTaskProducer: PhasedTaskProducer, TaskProducer
let buildingAnySwiftSourceFiles = (context.configuredTarget?.target as? BuildPhaseTarget)?.sourcesBuildPhase?.containsSwiftSources(context.workspaceContext.workspace, context, scope, context.filePathResolver) ?? false

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