Skip to content

Implement __SKIP_BUILD setting to prune test runners from builds for apple platforms #525

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

Merged
merged 1 commit into from
May 27, 2025
Merged
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
5 changes: 4 additions & 1 deletion Sources/SWBCore/Settings/BuiltinMacros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,8 @@ public final class BuiltinMacros {
// Info.plist Keys - Sticker Packs
public static let INFOPLIST_KEY_NSStickerSharingLevel = BuiltinMacros.declareEnumMacro("INFOPLIST_KEY_NSStickerSharingLevel") as EnumMacroDeclaration<StickerSharingLevel>

public static let __SKIP_BUILD = BuiltinMacros.declareBooleanMacro("__SKIP_BUILD")

// MARK: Built-in Macro Initialization

private static var initialized = false
Expand Down Expand Up @@ -2391,7 +2393,8 @@ public final class BuiltinMacros {
ENABLE_XOJIT_PREVIEWS,
BUILD_ACTIVE_RESOURCES_ONLY,
ENABLE_ONLY_ACTIVE_RESOURCES,
ENABLE_PLAYGROUND_RESULTS
ENABLE_PLAYGROUND_RESULTS,
__SKIP_BUILD
]

/// Force initialization of entitlements macros.
Expand Down
24 changes: 20 additions & 4 deletions Sources/SWBCore/TargetDependencyResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -553,18 +553,24 @@ fileprivate extension TargetDependencyResolver {
delegate.emit(.init(behavior: .error, location: .unknown, data: .init("The 'Skip Dependencies' option is deprecated and can no longer be used.")))
}

// Apply the requested dependency scope to the graph.
// Prune targets from the graph as needed.
var keptTargets: OrderedSet<ConfiguredTarget>
var removedTargets: OrderedSet<ConfiguredTarget>

// First, determine targets to be removed based on the dependency scope.
switch buildRequest.dependencyScope {
case .workspace:
// If dependencies are scoped to the workspace, no pruning is required.
keptTargets = allTargets
removedTargets = []
break
case .buildRequest:
if buildRequest.skipDependencies {
delegate.emit(.init(behavior: .error, location: .unknown, data: .init("The 'Skip Dependencies' option is deprecated and cannot be combined with dependency scopes.")))
}
// First, partition the targets into those we're keeping, and those we're removing.
var keptTargets: OrderedSet<ConfiguredTarget> = []
var removedTargets: OrderedSet<ConfiguredTarget> = []
keptTargets = []
removedTargets = []
let requestedTargetGUIDs = Set(buildRequest.buildTargets.map(\.target.guid))
var extraRequestedTargetGUIDs: Set<String> = []
var potentialExtraRequestedPackageTargetGUIDs: Set<String> = []
Expand Down Expand Up @@ -594,8 +600,18 @@ fileprivate extension TargetDependencyResolver {
removedTargets.append(configuredTarget)
}
}
}

// Then, remove targets based on the value of the __SKIP_BUILD setting.
let targetsToRemoveBasedOnSettings = keptTargets.filter { configuredTarget in
let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target)
return settings.globalScope.evaluate(BuiltinMacros.__SKIP_BUILD)
}
keptTargets.subtract(targetsToRemoveBasedOnSettings)
removedTargets.append(contentsOf: targetsToRemoveBasedOnSettings)

// For each removed target, identify all kept targets reachable by traversing only edges which originate at a removed target.
// For each removed target, identify all kept targets reachable by traversing only edges which originate at a removed target.
if !removedTargets.isEmpty {
var reachableKeptTargetsByRemovedTarget: [ConfiguredTarget: OrderedSet<ConfiguredTarget>] = [:]
do {
func visit(_ configuredTarget: ConfiguredTarget) {
Expand Down
121 changes: 96 additions & 25 deletions Tests/SWBCoreTests/DependencyScopingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import SWBTestSupport
import Foundation

@Suite fileprivate struct DependencyScopingTests: CoreBasedTests {
@Test(.requireSDKs(.macOS))
@Test(.requireSDKs(.host))
func buildRequestScopeBasics() async throws {
let core = try await getCore()

Expand Down Expand Up @@ -77,8 +77,8 @@ import Foundation

// Configure the targets and create a BuildRequest.
let buildParameters = BuildParameters(configuration: "Debug")
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T1")!)
let t2 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T2")!)
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T1")))
let t2 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T2")))
do {
let buildRequest = BuildRequest(parameters: buildParameters, buildTargets: [t1], dependencyScope: .buildRequest, continueBuildingAfterErrors: true, useParallelTargets: false, useImplicitDependencies: true, useDryRun: false)
let buildRequestContext = BuildRequestContext(workspaceContext: workspaceContext)
Expand All @@ -100,7 +100,78 @@ import Foundation
}
}

@Test(.requireSDKs(.macOS))
@Test(.requireSDKs(.host))
func skipBuildSetting() async throws {
let core = try await getCore()

let workspace = try TestWorkspace(
"Workspace",
projects: [
TestProject(
"P1",
groupTree: TestGroup(
"G1",
children: [
TestFile("S1.c"),
]
),
buildConfigurations: [
TestBuildConfiguration("Debug", buildSettings: [:]),
],
targets: [
TestStandardTarget(
"T1",
type: .framework,
buildConfigurations: [
TestBuildConfiguration("Debug", buildSettings: ["PRODUCT_NAME": "$(TARGET_NAME)", "__SKIP_BUILD": "YES"]),
],
buildPhases: [
TestSourcesBuildPhase(["S1.c"])
],
dependencies: ["T2"]
),
TestStandardTarget(
"T2",
type: .framework,
buildConfigurations: [
TestBuildConfiguration("Debug", buildSettings: ["PRODUCT_NAME": "$(TARGET_NAME)"]),
],
buildPhases: [
TestSourcesBuildPhase(["S1.c"])
],
dependencies: ["T3"]
),
TestStandardTarget(
"T3",
type: .framework,
buildConfigurations: [
TestBuildConfiguration("Debug", buildSettings: ["PRODUCT_NAME": "$(TARGET_NAME)"]),
],
buildPhases: [
TestSourcesBuildPhase(["S1.c"])
]
)
]
),
]
).load(core)
let workspaceContext = WorkspaceContext(core: core, workspace: workspace, processExecutionCache: .sharedForTesting)

// Configure the targets and create a BuildRequest.
let buildParameters = BuildParameters(configuration: "Debug")
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T1")))
let t2 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T2")))
do {
let buildRequest = BuildRequest(parameters: buildParameters, buildTargets: [t1], dependencyScope: .workspace, continueBuildingAfterErrors: true, useParallelTargets: false, useImplicitDependencies: true, useDryRun: false)
let buildRequestContext = BuildRequestContext(workspaceContext: workspaceContext)
let delegate = EmptyTargetDependencyResolverDelegate(workspace: workspaceContext.workspace)
let buildGraph = await TargetGraphFactory(workspaceContext: workspaceContext, buildRequest: buildRequest, buildRequestContext: buildRequestContext, delegate: delegate).graph(type: .dependency)
#expect(buildGraph.allTargets.map({ $0.target.name }) == ["T3", "T2"])
delegate.checkNoDiagnostics()
}
}

@Test(.requireSDKs(.host))
func buildRequestScopeRemovingInteriorTarget() async throws {
let core = try await getCore()

Expand Down Expand Up @@ -159,8 +230,8 @@ import Foundation

// Configure the targets and create a BuildRequest.
let buildParameters = BuildParameters(configuration: "Debug")
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T1")!)
let t3 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T3")!)
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T1")))
let t3 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T3")))

let buildRequest = BuildRequest(parameters: buildParameters, buildTargets: [t1, t3], dependencyScope: .buildRequest, continueBuildingAfterErrors: true, useParallelTargets: false, useImplicitDependencies: true, useDryRun: false)
let buildRequestContext = BuildRequestContext(workspaceContext: workspaceContext)
Expand All @@ -172,7 +243,7 @@ import Foundation
delegate.checkNoDiagnostics()
}

@Test(.requireSDKs(.macOS))
@Test(.requireSDKs(.host))
func buildRequestScopeRemovingGroupOfInteriorTargets() async throws {
let core = try await getCore()

Expand Down Expand Up @@ -287,12 +358,12 @@ import Foundation

// Configure the targets and create a BuildRequest.
let buildParameters = BuildParameters(configuration: "Debug")
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T1")!)
let t2 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T2")!)
let t3 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T3")!)
let t6 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T6")!)
let t7 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T7")!)
let t8 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T8")!)
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T1")))
let t2 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T2")))
let t3 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T3")))
let t6 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T6")))
let t7 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T7")))
let t8 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T8")))

let buildRequest = BuildRequest(parameters: buildParameters, buildTargets: [t1, t2, t3, t6, t7, t8], dependencyScope: .buildRequest, continueBuildingAfterErrors: true, useParallelTargets: false, useImplicitDependencies: true, useDryRun: false)
let buildRequestContext = BuildRequestContext(workspaceContext: workspaceContext)
Expand All @@ -308,7 +379,7 @@ import Foundation
delegate.checkNoDiagnostics()
}

@Test(.requireSDKs(.macOS))
@Test(.requireSDKs(.host))
func buildRequestScopeRemovingImplicitAndExplicitDependencies() async throws {
let core = try await getCore()

Expand Down Expand Up @@ -390,11 +461,11 @@ import Foundation

// Configure the targets and create a BuildRequest.
let buildParameters = BuildParameters(configuration: "Debug")
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T1")!)
let t2 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T2")!)
let t3 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T3")!)
let t4 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T4")!)
let t5 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T5")!)
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T1")))
let t2 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T2")))
let t3 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T3")))
let t4 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T4")))
let t5 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T5")))

do {
let buildRequest = BuildRequest(parameters: buildParameters, buildTargets: [t1, t2, t3], dependencyScope: .buildRequest, continueBuildingAfterErrors: true, useParallelTargets: false, useImplicitDependencies: true, useDryRun: false)
Expand Down Expand Up @@ -500,8 +571,8 @@ import Foundation

// Configure the targets and create a BuildRequest.
let buildParameters = BuildParameters(configuration: "Debug", activeRunDestination: .anyiOSDevice)
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T1")!)
let t3 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T3")!)
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T1")))
let t3 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T3")))

let buildRequest = BuildRequest(parameters: buildParameters, buildTargets: [t1, t3], dependencyScope: .buildRequest, continueBuildingAfterErrors: true, useParallelTargets: false, useImplicitDependencies: true, useDryRun: false)
let buildRequestContext = BuildRequestContext(workspaceContext: workspaceContext)
Expand All @@ -518,7 +589,7 @@ import Foundation
delegate.checkNoDiagnostics()
}

@Test(.requireSDKs(.macOS))
@Test(.requireSDKs(.host))
func buildRequestScopeWithPackages() async throws {
let core = try await getCore()

Expand Down Expand Up @@ -620,10 +691,10 @@ import Foundation

// Configure the targets and create a BuildRequest.
let buildParameters = BuildParameters(configuration: "Debug")
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "T1")!)
let t1 = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "T1")))

let somePackageProduct = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "SomePackageProduct")!)
let packageProductWithTransitiveRefs = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: workspace.target(named: "PackageProductWithTransitiveRefs")!)
let somePackageProduct = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "SomePackageProduct")))
let packageProductWithTransitiveRefs = BuildRequest.BuildTargetInfo(parameters: buildParameters, target: try #require(workspace.target(named: "PackageProductWithTransitiveRefs")))

do {
let buildRequest = BuildRequest(parameters: buildParameters, buildTargets: [t1], dependencyScope: .buildRequest, continueBuildingAfterErrors: true, useParallelTargets: false, useImplicitDependencies: true, useDryRun: false)
Expand Down
Loading