Skip to content

[6.2] swift-package-migrate: Miscellaneous low-risk improvements and more tests #8967

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 3 commits into from
Jul 25, 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
24 changes: 24 additions & 0 deletions Fixtures/SwiftMigrate/UpdateManifest/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// swift-tools-version:5.8

import PackageDescription

var swiftSettings: [SwiftSetting] = []

let package = Package(
name: "WithErrors",
targets: [
.target(
name: "CannotFindSettings",
swiftSettings: swiftSettings
),
.target(name: "A"),
.target(name: "B"),
]
)

package.targets.append(
.target(
name: "CannotFindTarget",
swiftSettings: swiftSettings
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// swift-tools-version:5.8

import PackageDescription

var swiftSettings: [SwiftSetting] = []

let package = Package(
name: "WithErrors",
targets: [
.target(
name: "CannotFindSettings",
swiftSettings: swiftSettings
),
.target(name: "A",swiftSettings: [
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InferIsolatedConformances"),]),
.target(name: "B",swiftSettings: [
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InferIsolatedConformances"),]),
]
)

package.targets.append(
.target(
name: "CannotFindTarget",
swiftSettings: swiftSettings
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// swift-tools-version:5.8

import PackageDescription

var swiftSettings: [SwiftSetting] = []

let package = Package(
name: "WithErrors",
targets: [
.target(
name: "CannotFindSettings",
swiftSettings: swiftSettings
),
.target(name: "A",swiftSettings: [
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InferIsolatedConformances"),]),
.target(name: "B"),
]
)

package.targets.append(
.target(
name: "CannotFindTarget",
swiftSettings: swiftSettings
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// swift-tools-version:5.8

import PackageDescription

var swiftSettings: [SwiftSetting] = []

let package = Package(
name: "WithErrors",
targets: [
.target(
name: "CannotFindSettings",
swiftSettings: swiftSettings
),
.target(name: "A",swiftSettings: [
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InferIsolatedConformances"),]),
.target(name: "B",swiftSettings: [
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InferIsolatedConformances"),]),
]
)

package.targets.append(
.target(
name: "CannotFindTarget",
swiftSettings: swiftSettings
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public func foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public func foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public func foo() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public func foo() {}
119 changes: 82 additions & 37 deletions Sources/Commands/PackageCommands/Migrate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import OrderedCollections

import PackageGraph
import PackageModel
import enum PackageModelSyntax.ManifestEditError

import SPMBuildCore
import SwiftFixIt
Expand All @@ -32,7 +33,7 @@ import var TSCBasic.stdoutStream
struct MigrateOptions: ParsableArguments {
@Option(
name: .customLong("target"),
help: "The targets to migrate to specified set of features."
help: "A comma-separated list of targets to migrate. (default: all Swift targets)"
)
var _targets: String?

Expand All @@ -42,7 +43,7 @@ struct MigrateOptions: ParsableArguments {

@Option(
name: .customLong("to-feature"),
help: "The Swift language upcoming/experimental feature to migrate to."
help: "A comma-separated list of Swift language features to migrate to."
)
var _features: String

Expand All @@ -64,31 +65,8 @@ extension SwiftPackageCommand {
var options: MigrateOptions

public func run(_ swiftCommandState: SwiftCommandState) async throws {
let toolchain = try swiftCommandState.productsBuildParameters.toolchain

let supportedFeatures = try Dictionary(
uniqueKeysWithValues: toolchain.swiftCompilerSupportedFeatures
.map { ($0.name, $0) }
)

// First, let's validate that all of the features are supported
// by the compiler and are migratable.

var features: [SwiftCompilerFeature] = []
for name in self.options.features {
guard let feature = supportedFeatures[name] else {
let migratableFeatures = supportedFeatures.map(\.value).filter(\.migratable).map(\.name)
throw ValidationError(
"Unsupported feature: \(name). Available features: \(migratableFeatures.joined(separator: ", "))"
)
}

guard feature.migratable else {
throw ValidationError("Feature '\(name)' is not migratable")
}

features.append(feature)
}
// First, validate and resolve the requested feature names.
let features = try self.resolveRequestedFeatures(swiftCommandState)

let targets = self.options.targets

Expand Down Expand Up @@ -176,9 +154,12 @@ extension SwiftPackageCommand {

// Once the fix-its were applied, it's time to update the
// manifest with newly adopted feature settings.
//
// Loop over a sorted array to produce deterministic results and
// order of diagnostics.

print("> Updating manifest")
for (name, _) in modules {
for name in modules.keys.sorted() {
swiftCommandState.observabilityScope.emit(debug: "Adding feature(s) to '\(name)'")
try self.updateManifest(
for: name,
Expand All @@ -188,6 +169,48 @@ extension SwiftPackageCommand {
}
}

/// Resolves the requested feature names.
///
/// - Returns: An array of resolved features, sorted by name.
private func resolveRequestedFeatures(
_ swiftCommandState: SwiftCommandState
) throws -> [SwiftCompilerFeature] {
let toolchain = try swiftCommandState.productsBuildParameters.toolchain

// Query the compiler for supported features.
let supportedFeatures = try toolchain.swiftCompilerSupportedFeatures

var resolvedFeatures: [SwiftCompilerFeature] = []

// Resolve the requested feature names, validating that they are
// supported by the compiler and migratable.
for name in self.options.features {
let feature = supportedFeatures.first { $0.name == name }

guard let feature else {
let migratableCommaSeparatedFeatures = supportedFeatures
.filter(\.migratable)
.map(\.name)
.sorted()
.joined(separator: ", ")

throw ValidationError(
"Unsupported feature '\(name)'. Available features: \(migratableCommaSeparatedFeatures)"
)
}

guard feature.migratable else {
throw ValidationError("Feature '\(name)' is not migratable")
}

resolvedFeatures.append(feature)
}

return resolvedFeatures.sorted { lhs, rhs in
lhs.name < rhs.name
}
}

private func createBuildSystem(
_ swiftCommandState: SwiftCommandState,
targets: OrderedSet<String>,
Expand Down Expand Up @@ -251,15 +274,37 @@ extension SwiftPackageCommand {
verbose: !self.globalOptions.logging.quiet
)
} catch {
try swiftCommandState.observabilityScope.emit(
error: """
Could not update manifest for '\(target)' (\(error)). \
Please enable '\(
features.map { try $0.swiftSettingDescription }
.joined(separator: ", ")
)' features manually
"""
)
var message =
"Could not update manifest to enable requested features for target '\(target)' (\(error))"

// Do not suggest manual addition if something else is wrong or
// if the error implies that it cannot be done.
if let error = error as? ManifestEditError {
switch error {
case .cannotFindPackage,
.cannotAddSettingsToPluginTarget,
.existingDependency:
break
case .cannotFindArrayLiteralArgument,
// This means the target could not be found
// syntactically, not that it does not exist.
.cannotFindTargets,
.cannotFindTarget,
// This means the swift-tools-version is lower than
// the version where one of the setting was introduced.
.oldManifest:
let settings = try features.map {
try $0.swiftSettingDescription
}.joined(separator: ", ")

message += """
. Please enable them manually by adding the following Swift settings to the target: \
'\(settings)'
"""
}
}

swiftCommandState.observabilityScope.emit(error: message)
}
}

Expand Down
Loading