Skip to content

Commit d99bce8

Browse files
hborlaxedin
andauthored
Add a defaultIsolation static method on SwiftSetting. (#8301)
Add a `defaultIsolation` static method on `SwiftSetting` to allow specifying default actor isolation within a module. ### Motivation: This is part of https://forums.swift.org/t/pitch-control-default-actor-isolation-inference/77482. Please leave feedback on the API shape in the pitch thread. ### Modifications: I added a `defaultIsolation` case to `TargetBuildSettingDescription` and a corresponding static method to `SwiftSetting`. ### Result: Programmers will be allowed to set `defaultIsolation` per target in a package manifest, e.g.: ```swift swiftSettings: [ .enableExperimentalFeature("StrictConcurrency"), .defaultIsolation(MainActor.self), ] ``` --------- Co-authored-by: Pavel Yaskevich <[email protected]>
1 parent 6f40b31 commit d99bce8

File tree

9 files changed

+284
-4
lines changed

9 files changed

+284
-4
lines changed

Sources/PackageDescription/BuildSettings.swift

+24
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,30 @@ public struct SwiftSetting: Sendable {
462462
return SwiftSetting(
463463
name: "swiftLanguageMode", value: [.init(describing: mode)], condition: condition)
464464
}
465+
466+
/// Set the default isolation to the given global actor type.
467+
///
468+
/// - Since: First available in PackageDescription 6.2.
469+
///
470+
/// - Parameters:
471+
/// - isolation: The type of global actor to use for default actor isolation
472+
/// inference. The only valid arguments are `MainActor.self` and `nil`.
473+
/// - condition: A condition that restricts the application of the build
474+
/// setting.
475+
@available(_PackageDescription, introduced: 6.2)
476+
public static func defaultIsolation(
477+
_ isolation: MainActor.Type?,
478+
_ condition: BuildSettingCondition? = nil
479+
) -> SwiftSetting {
480+
let isolationString =
481+
if isolation == nil {
482+
"nonisolated"
483+
} else {
484+
"MainActor"
485+
}
486+
return SwiftSetting(
487+
name: "defaultIsolation", value: [isolationString], condition: condition)
488+
}
465489
}
466490

467491
/// A linker build setting.

Sources/PackageLoading/ManifestJSONParser.swift

+9
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,15 @@ extension TargetBuildSettingDescription.Kind {
749749
}
750750

751751
return .swiftLanguageMode(version)
752+
case "defaultIsolation":
753+
guard let rawValue = values.first else {
754+
throw InternalError("invalid (empty) build settings value")
755+
}
756+
guard let isolation = TargetBuildSettingDescription.DefaultIsolation(rawValue: rawValue) else {
757+
throw InternalError("unknown default isolation: \(rawValue)")
758+
}
759+
760+
return .defaultIsolation(isolation)
752761
default:
753762
throw InternalError("invalid build setting \(name)")
754763
}

Sources/PackageLoading/PackageBuilder.swift

+11
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,17 @@ public final class PackageBuilder {
12521252
}
12531253

12541254
values = [version.rawValue]
1255+
1256+
case .defaultIsolation(let isolation):
1257+
switch setting.tool {
1258+
case .c, .cxx, .linker:
1259+
throw InternalError("only Swift supports default isolation")
1260+
1261+
case .swift:
1262+
decl = .OTHER_SWIFT_FLAGS
1263+
}
1264+
1265+
values = ["-default-isolation", isolation.rawValue]
12551266
}
12561267

12571268
// Create an assignment for this setting.

Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift

+9-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ public enum TargetBuildSettingDescription {
2525
case Cxx
2626
}
2727

28+
public enum DefaultIsolation: String, Codable, Hashable, Sendable {
29+
case MainActor
30+
case nonisolated
31+
}
32+
2833
/// The kind of the build setting, with associate configuration
2934
public enum Kind: Codable, Hashable, Sendable {
3035
case headerSearchPath(String)
@@ -42,13 +47,16 @@ public enum TargetBuildSettingDescription {
4247

4348
case swiftLanguageMode(SwiftLanguageVersion)
4449

50+
case defaultIsolation(DefaultIsolation)
51+
4552
public var isUnsafeFlags: Bool {
4653
switch self {
4754
case .unsafeFlags(let flags):
4855
// If `.unsafeFlags` is used, but doesn't specify any flags, we treat it the same way as not specifying it.
4956
return !flags.isEmpty
5057
case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .interoperabilityMode,
51-
.enableUpcomingFeature, .enableExperimentalFeature, .strictMemorySafety, .swiftLanguageMode:
58+
.enableUpcomingFeature, .enableExperimentalFeature, .strictMemorySafety, .swiftLanguageMode,
59+
.defaultIsolation:
5260
return false
5361
}
5462
}

Sources/PackageModel/ManifestSourceGeneration.swift

+13
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,17 @@ fileprivate extension SourceCodeFragment {
573573
params.append(SourceCodeFragment(from: condition))
574574
}
575575
self.init(enum: setting.kind.name, subnodes: params)
576+
case .defaultIsolation(let isolation):
577+
switch isolation {
578+
case .MainActor:
579+
params.append(SourceCodeFragment("MainActor.self"))
580+
case .nonisolated:
581+
params.append(SourceCodeFragment("nil"))
582+
}
583+
if let condition = setting.condition {
584+
params.append(SourceCodeFragment(from: condition))
585+
}
586+
self.init(enum: setting.kind.name, subnodes: params)
576587
}
577588
}
578589

@@ -1010,6 +1021,8 @@ extension TargetBuildSettingDescription.Kind {
10101021
return "strictMemorySafety"
10111022
case .swiftLanguageMode:
10121023
return "swiftLanguageMode"
1024+
case .defaultIsolation:
1025+
return "defaultIsolation"
10131026
}
10141027
}
10151028
}

Tests/BuildTests/BuildPlanTests.swift

+10-3
Original file line numberDiff line numberDiff line change
@@ -4308,6 +4308,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
43084308
condition: .init(platformNames: ["macos"], config: "debug")
43094309
),
43104310
.init(tool: .swift, kind: .strictMemorySafety),
4311+
.init(tool: .swift, kind: .defaultIsolation(.MainActor)),
43114312
]
43124313
),
43134314
TargetDescription(
@@ -4341,6 +4342,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
43414342
condition: .init(platformNames: ["macos"])
43424343
),
43434344
.init(tool: .linker, kind: .unsafeFlags(["-Ilfoo", "-L", "lbar"])),
4345+
.init(tool: .swift, kind: .defaultIsolation(.nonisolated)),
43444346
]
43454347
),
43464348
TargetDescription(
@@ -4433,6 +4435,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
44334435
"-Xcc", "-std=c++17",
44344436
"-enable-upcoming-feature", "BestFeature",
44354437
"-strict-memory-safety",
4438+
"-default-isolation", "MainActor",
44364439
"-g",
44374440
"-Xcc", "-g",
44384441
"-Xcc", "-fno-omit-frame-pointer",
@@ -4441,7 +4444,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
44414444
)
44424445

44434446
let exe = try result.moduleBuildDescription(for: "exe").swift().compileArguments()
4444-
XCTAssertMatch(exe, [.anySequence, "-swift-version", "5", "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end])
4447+
XCTAssertMatch(exe, [.anySequence, "-swift-version", "5", "-DFOO", "-default-isolation", "nonisolated", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end])
44454448

44464449
let linkExe = try result.buildProduct(for: "exe").linkArguments()
44474450
XCTAssertMatch(linkExe, [.anySequence, "-lsqlite3", "-llibz", "-Ilfoo", "-L", "lbar", "-g", .end])
@@ -4498,6 +4501,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
44984501
"-enable-upcoming-feature",
44994502
"BestFeature",
45004503
"-strict-memory-safety",
4504+
"-default-isolation", "MainActor",
45014505
"-g",
45024506
"-Xcc", "-g",
45034507
"-Xcc", "-fomit-frame-pointer",
@@ -4506,7 +4510,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
45064510
)
45074511

45084512
let exe = try result.moduleBuildDescription(for: "exe").swift().compileArguments()
4509-
XCTAssertMatch(exe, [.anySequence, "-swift-version", "5", "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fomit-frame-pointer", .end])
4513+
XCTAssertMatch(exe, [.anySequence, "-swift-version", "5", "-DFOO", "-default-isolation", "nonisolated", "-g", "-Xcc", "-g", "-Xcc", "-fomit-frame-pointer", .end])
45104514
}
45114515

45124516
// omit frame pointers explicitly set to false
@@ -4554,6 +4558,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
45544558
"-enable-upcoming-feature",
45554559
"BestFeature",
45564560
"-strict-memory-safety",
4561+
"-default-isolation", "MainActor",
45574562
"-g",
45584563
"-Xcc", "-g",
45594564
"-Xcc", "-fno-omit-frame-pointer",
@@ -4562,7 +4567,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
45624567
)
45634568

45644569
let exe = try result.moduleBuildDescription(for: "exe").swift().compileArguments()
4565-
XCTAssertMatch(exe, [.anySequence, "-swift-version", "5", "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end])
4570+
XCTAssertMatch(exe, [.anySequence, "-swift-version", "5", "-DFOO", "-default-isolation", "nonisolated", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end])
45664571
}
45674572

45684573
do {
@@ -4599,6 +4604,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
45994604
"-enable-upcoming-feature", "BestFeature",
46004605
"-enable-upcoming-feature", "WorstFeature",
46014606
"-strict-memory-safety",
4607+
"-default-isolation", "MainActor",
46024608
"-g",
46034609
"-Xcc", "-g",
46044610
.end,
@@ -4614,6 +4620,7 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
46144620
"-DFOO",
46154621
"-cxx-interoperability-mode=default",
46164622
"-Xcc", "-std=c++17",
4623+
"-default-isolation", "nonisolated",
46174624
"-g",
46184625
"-Xcc", "-g",
46194626
.end,

Tests/PackageLoadingTests/PackageBuilderTests.swift

+70
Original file line numberDiff line numberDiff line change
@@ -3185,6 +3185,76 @@ final class PackageBuilderTests: XCTestCase {
31853185
}
31863186
}
31873187
}
3188+
3189+
func testDefaultIsolationPerTarget() throws {
3190+
let fs = InMemoryFileSystem(emptyFiles:
3191+
"/Sources/A/a.swift",
3192+
"/Sources/B/b.swift"
3193+
)
3194+
3195+
let manifest = Manifest.createRootManifest(
3196+
displayName: "pkg",
3197+
toolsVersion: .v6_2,
3198+
targets: [
3199+
try TargetDescription(
3200+
name: "A",
3201+
settings: [
3202+
.init(tool: .swift, kind: .defaultIsolation(.MainActor))
3203+
]
3204+
),
3205+
try TargetDescription(
3206+
name: "B",
3207+
settings: [
3208+
.init(tool: .swift, kind: .defaultIsolation(.nonisolated), condition: .init(platformNames: ["linux"])),
3209+
.init(tool: .swift, kind: .defaultIsolation(.MainActor), condition: .init(platformNames: ["macos"], config: "debug"))
3210+
]
3211+
),
3212+
]
3213+
)
3214+
3215+
PackageBuilderTester(manifest, in: fs) { package, _ in
3216+
package.checkModule("A") { package in
3217+
let macosDebugScope = BuildSettings.Scope(
3218+
package.target.buildSettings,
3219+
environment: BuildEnvironment(platform: .macOS, configuration: .debug)
3220+
)
3221+
XCTAssertMatch(macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS),
3222+
[.anySequence, "-default-isolation", "MainActor", .anySequence])
3223+
3224+
let macosReleaseScope = BuildSettings.Scope(
3225+
package.target.buildSettings,
3226+
environment: BuildEnvironment(platform: .macOS, configuration: .release)
3227+
)
3228+
XCTAssertMatch(macosReleaseScope.evaluate(.OTHER_SWIFT_FLAGS),
3229+
[.anySequence, "-default-isolation", "MainActor", .anySequence])
3230+
3231+
}
3232+
3233+
package.checkModule("B") { package in
3234+
let linuxDebugScope = BuildSettings.Scope(
3235+
package.target.buildSettings,
3236+
environment: BuildEnvironment(platform: .linux, configuration: .debug)
3237+
)
3238+
XCTAssertMatch(linuxDebugScope.evaluate(.OTHER_SWIFT_FLAGS),
3239+
[.anySequence, "-default-isolation", "nonisolated", .anySequence])
3240+
3241+
let macosDebugScope = BuildSettings.Scope(
3242+
package.target.buildSettings,
3243+
environment: BuildEnvironment(platform: .macOS, configuration: .debug)
3244+
)
3245+
XCTAssertMatch(macosDebugScope.evaluate(.OTHER_SWIFT_FLAGS),
3246+
[.anySequence, "-default-isolation", "MainActor", .anySequence])
3247+
3248+
let macosReleaseScope = BuildSettings.Scope(
3249+
package.target.buildSettings,
3250+
environment: BuildEnvironment(platform: .macOS, configuration: .release)
3251+
)
3252+
XCTAssertNoMatch(macosReleaseScope.evaluate(.OTHER_SWIFT_FLAGS),
3253+
[.anySequence, "-default-isolation", "MainActor", .anySequence])
3254+
3255+
}
3256+
}
3257+
}
31883258
}
31893259

31903260
final class PackageBuilderTester {

Tests/WorkspaceTests/ManifestSourceGenerationTests.swift

+34
Original file line numberDiff line numberDiff line change
@@ -858,4 +858,38 @@ final class ManifestSourceGenerationTests: XCTestCase {
858858
let contents = try manifest.generateManifestFileContents(packageDirectory: manifest.path.parentDirectory)
859859
try await testManifestWritingRoundTrip(manifestContents: contents, toolsVersion: .v6_0)
860860
}
861+
862+
func testDefaultIsolation() async throws {
863+
let manifest = Manifest.createRootManifest(
864+
displayName: "pkg",
865+
path: "/pkg",
866+
toolsVersion: .v6_2,
867+
dependencies: [],
868+
targets: [
869+
try TargetDescription(
870+
name: "A",
871+
type: .executable,
872+
settings: [
873+
.init(tool: .swift, kind: .defaultIsolation(.nonisolated))
874+
]
875+
),
876+
try TargetDescription(
877+
name: "B",
878+
type: .executable,
879+
settings: [
880+
.init(tool: .swift, kind: .defaultIsolation(.MainActor))
881+
]
882+
),
883+
try TargetDescription(
884+
name: "conditional",
885+
type: .executable,
886+
settings: [
887+
.init(tool: .swift, kind: .defaultIsolation(.nonisolated), condition: .init(platformNames: ["linux"])),
888+
.init(tool: .swift, kind: .defaultIsolation(.MainActor), condition: .init(platformNames: ["macos"], config: "debug"))
889+
]
890+
)
891+
])
892+
let contents = try manifest.generateManifestFileContents(packageDirectory: manifest.path.parentDirectory)
893+
try await testManifestWritingRoundTrip(manifestContents: contents, toolsVersion: .v6_2)
894+
}
861895
}

0 commit comments

Comments
 (0)