# Pitch: \[SwiftPM\] Swift Language Version Per Target

**URL:** <https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067>\
**Category:** Pitches\
**Created:** [April 3, 2024, 6:32pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067 "2024-04-03T18:32:44Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![xedin](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/xedin/32/309_2.png) [@xedin](https://forums.swift.org/u/xedin)\
**Post date:** [April 3, 2024, 6:32pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/1 "2024-04-03T18:32:44Z")

</div>

Hello, Swift Evolution!

I'd like us to consider the following addition to the Swift Package Manager APIs.

* * *

## Introduction

The current Swift Package Manager manifest API for specifying Swift language version(s) applies to an entire package which is limiting when adopting new language versions that have implications for source compatibility.

## Motivation

Adopting new language versions at the target granularity allows for gradual migration to prevent possible disruptions. Swift 6, for example, turns on strict concurrency by default, which can have major implications for the project in the form of new errors that were previously downgraded to warnings. SwiftPM should allow to specify a language version per target so that package authors can incrementally transition their project to the newer version.

## Proposed solution

Add a new Swift target setting API, similar to `enable{Upcoming, Experimental}Feature`, to specify a Swift language version that should be used to build the target, if such version is not specified, fallback to the current language version determination logic.

## Detailed design

Add a new `swiftVersion` API to `SwiftSetting` limited to manifests \>= 6.0:

```swift
public struct SwiftSetting {
  // ... other settings
  
  @available(_PackageDescription, introduced: 6.0)
  public static func swiftVersion(
      _ version: SwiftVersion,
      _ condition: BuildSettingCondition? = nil
  ) -> SwiftSetting {
     ...
  }
}

```

## Security

New language version setting has no implications on security, safety or privacy.

## Impact on existing packages

Since this is a new API, all existing packages will use the default behavior - version specified at the package level when set or determined based on the tools version.

## Alternatives considered

Add a new setting for 'known-safe' flags, of which `-swift-version` could be the first. This seems less user-friendly and error prone than re-using `SwiftLanguageVersion`, which has known language versions as its cases (with a plaintext escape hatch when necessary).

---

<div class="post-metadata">

**Author:** ![hborla](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hborla/32/27563_2.png) [@hborla](https://forums.swift.org/u/hborla)\
**Post date:** [April 3, 2024, 6:50pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/2 "2024-04-03T18:50:28Z")

</div>

I think this is an important pitch, because incremental migration per-target is critical for the Swift 6 migration, including for packages. I have a few minor comments/questions

> [@xedin](#):
>
> ```swift
> public static func swiftVersion(
> 
> ```

Should we call this `swiftLanguageVersion` since the term "Swift version" is ambiguous between "language" and "tools"?

> [@xedin](#):
>
> Since this is a new API, all existing packages will use the default behavior - version specified at the package level when set or determined based on the tools version.

It's worth clarifying what the default language version actually is when using Swift 6.0 tools, because I think it's debatable. Personally, I don't think the default when using Swift 6.0 tools should be Swift 6 mode - that's not the default in the compiler, because otherwise source compatibility won't be maintained for existing code. I can see an argument that _maybe_ the SwiftPM behavior should be different because you need to explicitly change the package manifest to require 6.0 tools, but I also don't think using 6.0 tools is an indication that the package wants to use the Swift 6 language mode by default.

> [@xedin](#):
>
> Add a new setting for 'known-safe' flags, of which `-swift-version` could be the first. This seems less user-friendly and error prone than re-using `SwiftLanguageVersion`, which has known language versions as its cases (with a plaintext escape hatch when necessary).

I agree with the choice to use `SwiftLanguageVersion` in the API here. Will you also add a `v6` static let? e.g.

```swift
    /// Swift language version 6.
    public static let v6 = SwiftLanguageVersion(uncheckedString: "6")

```

---

<div class="post-metadata">

**Author:** ![jrose](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jrose/32/408_2.png) [@jrose](https://forums.swift.org/u/jrose)\
**Post date:** [April 3, 2024, 6:54pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/3 "2024-04-03T18:54:39Z")

</div>

> [@hborla](#):
>
> Personally, I don't think the default when using Swift 6.0 tools should be Swift 6 mode - that's not the default in the compiler, because otherwise source compatibility won't be maintained for existing code.

I’ll push back on this: if the default mode is _not_ Swift 6 based on a 6.0 PackageDescription, then every _new_ package will have to specify Swift 6 mode multiple times. Certainly new versions need to not break source compatibility, but it’s also nice when the “default” settings are the ideal ones for a user who doesn’t even know there’s an important setting to change. Maybe PackageDescription versions shouldn’t _always_ be treated as “epochs” like this that change defaults, but one with a major version number in it probably wouldn’t surprise anyone.

---

<div class="post-metadata">

**Author:** ![hborla](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hborla/32/27563_2.png) [@hborla](https://forums.swift.org/u/hborla)\
**Post date:** [April 3, 2024, 6:55pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/4 "2024-04-03T18:55:57Z")

</div>

> [@jrose](#):
>
> I’ll push back on this: if the default mode is _not_ Swift 6 based on a 6.0 PackageDescription, then every _new_ package will have to specify Swift 6 mode multiple times.

I don't think that's a given. We could change `swift package init` for 6.0 tools to specify the language version to be Swift 6 for the entire package, not using this new per-target API.

---

<div class="post-metadata">

**Author:** ![xedin](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/xedin/32/309_2.png) [@xedin](https://forums.swift.org/u/xedin)\
**Post date:** [April 3, 2024, 6:56pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/5 "2024-04-03T18:56:12Z")

</div>

> Should we call this `swiftLanguageVersion` since the term "Swift version" is ambiguous between "language" and "tools"?

Sounds good!

> I agree with the choice to use `SwiftLanguageVersion` in the API here. Will you also add a `v6` static let? e.g.

This a actually my mistake we have multiple SwiftVersion in different places and one SwiftLanguageVersion. I modified the text to use `SwiftVersion` enum from `LanguageStandardSettings` and yes, I'll add new `v6` case for it!

---

<div class="post-metadata">

**Author:** ![jrose](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jrose/32/408_2.png) [@jrose](https://forums.swift.org/u/jrose)\
**Post date:** [April 3, 2024, 7:01pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/6 "2024-04-03T19:01:18Z")

</div>

True, though that still ends up being _two_ places. I personally don’t tend to use `swift package init`; is it assumed that most people do?

---

<div class="post-metadata">

**Author:** ![Lancelotbronner](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lancelotbronner/32/22071_2.png) [@Lancelotbronner](https://forums.swift.org/u/Lancelotbronner)\
**Post date:** [April 3, 2024, 7:20pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/7 "2024-04-03T19:20:03Z")

</div>

Do you think it would be feasible to include C and C++ versions with this change?

---

<div class="post-metadata">

**Author:** ![allevato](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/allevato/32/385_2.png) [@allevato](https://forums.swift.org/u/allevato)\
**Post date:** [April 3, 2024, 8:09pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/8 "2024-04-03T20:09:46Z")

</div>

> [@Lancelotbronner](#):
>
> Do you think it would be feasible to include C and C++ versions with this change?

This would be going down a fragile path as Swift and SPM continue making progress toward explicit modules. In an explicit module build, the C/C++ language mode _must_ be the same between any two modules that are connected in the dependency graph (unless you use something like `@_implementationOnly import` to prune the subgraph under a Swift module, but that isn't fully supported yet).

---

<div class="post-metadata">

**Author:** ![bnbarham](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/bnbarham/32/17708_2.png) [@bnbarham](https://forums.swift.org/u/bnbarham)\
**Post date:** [April 3, 2024, 8:21pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/9 "2024-04-03T20:21:34Z")

</div>

> [@hborla](#):
>
> It's worth clarifying what the default language version actually is when using Swift 6.0 tools, because I think it's debatable. Personally, I don't think the default when using Swift 6.0 tools should be Swift 6 mode - that's not the default in the compiler, because otherwise source compatibility won't be maintained for existing code.

The current behavior today is that 5 is used in that case, see `ToolsVersion.swiftLanguageVersion`. I suspect that's only because we haven't updated it though. IMO a `swift-tools-version: 6.0` package should use `6` as its language version by default - a top level `swiftLanguageVersion` can always be used to specify `5`, with the proposed target level to update specific targets to `6` for migration purposes.

That would then mean that the default makes sense for the majority of users and those wanting to migrate have the means to do so.

---

<div class="post-metadata">

**Author:** ![Lancelotbronner](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lancelotbronner/32/22071_2.png) [@Lancelotbronner](https://forums.swift.org/u/Lancelotbronner)\
**Post date:** [April 3, 2024, 8:47pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/10 "2024-04-03T20:47:51Z")

</div>

My use case is typically say a C89 library used in a C11 or C23 executable, I currently split them into different packages.

I've also had similar cases with C89 libraries and a more modern C wrapper for a Swift interface, which I'm also splitting into different packages.

I was hoping this could help me clean that up a little!

(I often use SPM with C-only projects so I guess my use case isn't the most common!)

---

<div class="post-metadata">

**Author:** ![tgoyne](https://avatars.discourse-cdn.com/v4/letter/t/5e9695/32.png) [@tgoyne](https://forums.swift.org/u/tgoyne)\
**Post date:** [April 3, 2024, 8:50pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/11 "2024-04-03T20:50:57Z")

</div>

> [@allevato](#):
>
> This would be going down a fragile path as Swift and SPM continue making progress toward explicit modules. In an explicit module build, the C/C++ language mode _must_ be the same between any two modules that are connected in the dependency graph (unless you use something like `@_implementationOnly import` to prune the subgraph under a Swift module, but that isn't fully supported yet).

That sounds like a very significant problem that needs to be fixed? Anything that makes it so that two unrelated packages can trivially conflict with each other is going to make things very brittle.

---

<div class="post-metadata">

**Author:** ![James\_Dempsey](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/james_dempsey/32/14336_2.png) [@James\_Dempsey](https://forums.swift.org/u/James_Dempsey)\
**Post date:** [April 3, 2024, 9:08pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/12 "2024-04-03T21:08:58Z")

</div>

> [@bnbarham](#):
>
> > [@hborla](#):
> >
> > It's worth clarifying what the default language version actually is when using Swift 6.0 tools, because I think it's debatable. Personally, I don't think the default when using Swift 6.0 tools should be Swift 6 mode - that's not the default in the compiler, because otherwise source compatibility won't be maintained for existing code.
> 
> The current behavior today is that 5 is used in that case, see `ToolsVersion.swiftLanguageVersion`. I suspect that's only because we haven't updated it though. IMO a `swift-tools-version: 6.0` package should use `6` as its language version by default - a top level `swiftLanguageVersion` can always be used to specify `5`, with the proposed target level to update specific targets to `6` for migration purposes.
> 
> That would then mean that the default makes sense for the majority of users and those wanting to migrate have the means to do so.

I agree adding this as a per-target Swift setting that is not an 'unsafe flag' is important for the transition to Swift language version 6.

But I also think it's reasonable to expect if `swift-tools-version` is a 5.x value that the default Swift language version would be 5 (as it currently is) and that updating the `swift-tools-version` in the manifest to 6.0 would use Swift language version 6 as the default. (I also would be completely surprised if making a new package today compiled using Swift language version 4.2 or 4)

A developer would need to explicitly update the manifest to `swift-tools-version: 6.0` to change the default, so without making that change, existing packages would continue to build using Swift language version 5. I believe that means source compatibility would be maintained for all existing packages.

Once moving to `swift-tools-version: 6.0` all targets could continue to be built with Swift language version 5 using the existing package-level argument:

```swift
    swiftLanguageVersions: [.v5]

```

And then, as a target is migrated, it would add the proposed Swift setting:

```swift
     swiftSettings: [.swiftLanguageVersion(.v6)]

```

And then, once fully migrated, all of these could be removed from the manifest.

I think this allows for source compatibility for existing packages and then no specification required after migration, while giving precise control using a fairly small number of manifest changes during migration.

(Unrelated to the pitch: I also wonder if it might be possible if during a transition period (maybe for the first year after 6.0 is released?) `swift package init` might ask interactively which language version, 5 or 6 is desired?

If 5, the `swiftLanguageVersions: [.v5]` could be automatically added to the generated manifest.)

---

<div class="post-metadata">

**Author:** ![James\_Dempsey](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/james_dempsey/32/14336_2.png) [@James\_Dempsey](https://forums.swift.org/u/James_Dempsey)\
**Post date:** [April 3, 2024, 10:20pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/13 "2024-04-03T22:20:20Z")

</div>

It also occurred to me that, of course, a developer could alternatively:

- Move to `swift-tools-version: 6.0`
- **Not** add a package-level language version setting
- Add `swiftSettings: [.swiftLanguageVersion(.v5)]` to each target
- Migrate each target and remove the per-target setting

The combination of this proposed setting and upcoming feature flags really gives an amazing amount of granular flexibility in migrating.

Thinking back on the Swift 3 and Swift 4 migrations, the Swift ecosystem has come a long way!

---

<div class="post-metadata">

**Author:** ![ole](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ole/32/18720_2.png) [@ole](https://forums.swift.org/u/ole)\
**Post date:** [April 4, 2024, 11:50am UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/14 "2024-04-04T11:50:38Z")

</div>

+1 on the general idea of the pitch. It's important to have per-target control over the Swift language version.

> [@hborla](#):
>
> Personally, I don't think the default when using Swift 6.0 tools should be Swift 6 mode - that's not the default in the compiler, because otherwise source compatibility won't be maintained for existing code.

Apologies if this isn't the thread to debate this point, but I find it very surprising that the default language version of the Swift 6.0 compiler is not Swift 6. Is this the plan for the actual Swift 6.0 release?

Does this mean that going forward, we'll have to pass `-swift-version 6` to all CLI `swift`/`swiftc` invocations if we want to use Swift 6 features? I would find that very odd. Or is there a plan to change the default in a later Swift 6.x compiler release? (which would also be odd)

---

<div class="post-metadata">

**Author:** ![hassila](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hassila/32/22647_2.png) [@hassila](https://forums.swift.org/u/hassila)\
**Post date:** [April 4, 2024, 12:03pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/15 "2024-04-04T12:03:16Z")

</div>

I’ll not elaborate the points already made on why it would make sense to default the language version to the toolchain version, but just add two more minor point in favor of that:

1. If we want people to migrate to the latest language version defaulting to it is crucial over time as we’d expect usage of swift to grow
2. More importantly, it would set a strange precedent for future major language versions. If we’d default to swift 5 for the swift 6 toolchain, what should be the default for the swift 7 toolchain (and why?). Much clearer to link those and provide the escape hatches for migration outlined above.

---

<div class="post-metadata">

**Author:** ![Sajjon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/sajjon/32/12044_2.png) [@Sajjon](https://forums.swift.org/u/Sajjon)\
**Post date:** [April 4, 2024, 12:16pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/16 "2024-04-04T12:16:14Z")

</div>

Every single SPM package I've created the last few years (many hundreds of them, since it is what I use as "playground") I have created with `swift package init` - so I'm one data point (weighted with many packages! 🙂 )

---

<div class="post-metadata">

**Author:** ![hborla](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hborla/32/27563_2.png) [@hborla](https://forums.swift.org/u/hborla)\
**Post date:** [April 5, 2024, 12:56am UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/17 "2024-04-05T00:56:28Z")

</div>

> [@ole](#):
>
> Apologies if this isn't the thread to debate this point, but I find it very surprising that the default language version of the Swift 6.0 compiler is not Swift 6.

I'm happy to discuss what lead to keeping the default at the Swift 5 language mode and the implications of that, but it's not really related to this pitch so I think we should start a new discussion thread.

---

<div class="post-metadata">

**Author:** ![wes1](https://avatars.discourse-cdn.com/v4/letter/w/848f3c/32.png) [@wes1](https://forums.swift.org/u/wes1)\
**Post date:** [April 5, 2024, 1:13am UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/18 "2024-04-05T01:13:30Z")

</div>

> [@James\_Dempsey](#):
>
> a developer could alternatively:
> 
> - Move to `swift-tools-version: 6.0`
> - **Not** add a package-level language version setting
> - Add `swiftSettings: [.swiftLanguageVersion(.v5)]` to each target

Interesting - trailing targets opt-in by removing the restriction. But that means they can't build with 5.x toolchains.

The proposal limits `swiftLanguageVersion(...)` to 6.0+ manifests, but would it help if \>5.10 tools supported it as well?

(Assuming that's in scope for this proposal and that \>5.10 is possible...)

With `@available()` we can selectively make new API's unavailable in older language contexts. But still when library developers want to upgrade to 6.x but still support 5.x builds, they're forced to branch, create and release different versions, and all clients are forced to manually update dependencies to pick up the new version.

An alternative way is to have a separate "libv6" target that can be incrementally adopted (even automatically via indirection if the API is compatible). In this case, tools \<6.0 but \>5.10 would ignore targets with `.swiftLanguageVersion(.v6)`, but v6+ toolchains could build them.

Given 4 specifications (?: 2 optional):

| Locus | Specification |
| --- | --- |
| toolchain | (`which swift`) |
| Package.swift | `// swift-tools-version: N.N` |
| package ? | `swiftLanguageVersions: [.vN]` |
| target ? | `swiftSettings: [.swiftLanguageVersion(.vN)]` |

where:

- unstated target defaults to package
- unstated package defaults to Package.swift
- Package.swift \<= toolchain (??)

Supporting target.v6 in 5.10+ toolchain adds two cases:

| Use-case | ToolChain | Package.swift | package | target |
| --- | --- | --- | --- | --- |
| defaults | 4-6 | 4-6 | v4-6, -- | -- |
| leading target | 6 | 6.0 | .v5 | .v6 |
| trailing target | 6 | 6.0 | .v6, -- | .v5 |
| aspiring target | 6 | 5.10.1 | -- | .v6 |
| 5.x compatible | \>= 5.10.1 | 5.10.1 | -- | .v6 - ignored |

Note

- `--` means unstated

Issues:

1. From the SCM standpoint this means the build uses different code for the same SCM coordinates (package+target+version). That's troubling, but Swift does that already with `@available` and the target triple (and debug vs. release).

2. For explicit clients of target libv6 accidentally building with 5.10.1 toolchain, we'd prefer a clear error that they need a 6.x toolchain.

---

<div class="post-metadata">

**Author:** ![hassila](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hassila/32/22647_2.png) [@hassila](https://forums.swift.org/u/hassila)\
**Post date:** [April 5, 2024, 6:47am UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/19 "2024-04-05T06:47:50Z")

</div>

I created a new thread for that:

> [@Default swift language version for a given toolchain version](https://forums.swift.org/t/default-swift-language-version-for-a-given-toolchain-version/71100):
>
> To follow up from [Pitch: [SwiftPM] Swift Language Version Per Target](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067) To break out the discussion about what the default language version should be for a given toolchain version to a new thread;

---

<div class="post-metadata">

**Author:** ![James\_Dempsey](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/james_dempsey/32/14336_2.png) [@James\_Dempsey](https://forums.swift.org/u/James_Dempsey)\
**Post date:** [April 5, 2024, 1:26pm UTC](https://forums.swift.org/t/pitch-swiftpm-swift-language-version-per-target/71067/20 "2024-04-05T13:26:52Z")

</div>

My understanding is a package with an existing `Package.swift` file using a 5.x tool version can add a `Package@swift-6.0.swift` manifest to be used with Swift tools 6.0 and later.

The correct manifest will be used based on the toolchain version used to build the package.

To help avoid needing completely different branches to support different language mode versions, you can also use conditional compilation using `#if swift(<6)` or `#if swift(>=6)` to allow different code to be compiled depending on the Swift language version.

I don’t know if these are fully able to address the issues you describe in your post.
