From 0c1b2c67549fbe7bb2d3dc2559f0365fac64688d Mon Sep 17 00:00:00 2001 From: Mirza Ucanbarlic <56406159+supersonicbyte@users.noreply.github.com> Date: Fri, 5 Sep 2025 16:42:44 +0200 Subject: [PATCH 1/5] fix linker flags for dynamic libraries when executing command plugin --- Sources/Build/BuildDescription/ProductBuildDescription.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Build/BuildDescription/ProductBuildDescription.swift b/Sources/Build/BuildDescription/ProductBuildDescription.swift index b07da282921..ca8b0030323 100644 --- a/Sources/Build/BuildDescription/ProductBuildDescription.swift +++ b/Sources/Build/BuildDescription/ProductBuildDescription.swift @@ -167,7 +167,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription args += ["-L", self.buildParameters.buildPath.pathString] args += try ["-o", binaryPath.pathString] args += ["-module-name", self.product.name.spm_mangledToC99ExtendedIdentifier()] - args += self.dylibs.map { "-l" + $0.product.name } + args += self.dylibs.map { "-l" + $0.product.name + $0.buildParameters.suffix } // Add arguments needed for code coverage if it is enabled. if self.buildParameters.testingParameters.enableCodeCoverage { From fe7f921b49f986250654390749f55dbc28c0ca96 Mon Sep 17 00:00:00 2001 From: Mirza Ucanbarlic <56406159+supersonicbyte@users.noreply.github.com> Date: Fri, 5 Sep 2025 18:56:38 +0200 Subject: [PATCH 2/5] add test for command plugin dynamic dependencies --- Tests/CommandsTests/PackageCommandTests.swift | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index 0c6cf09e023..02ec2b36eb2 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -6948,5 +6948,172 @@ struct PackageCommandTests { expectNoDiagnostics(observability.diagnostics) } } + + @Test(arguments: getBuildData(for: SupportedBuildSystemOnAllPlatforms)) + func commandPluginDynamicDependencies( + buildData: BuildData + ) async throws { + try await testWithTemporaryDirectory { tmpPath in + // Create a sample package with a command plugin that has a dynamic dependency. + let packageDir = tmpPath.appending(components: "MyPackage") + try localFileSystem.writeFileContents( + packageDir.appending(components: "Package.swift"), + string: + """ + // swift-tools-version: 6.0 + // The swift-tools-version declares the minimum version of Swift required to build this package. + + import PackageDescription + + let package = Package( + name: "command-plugin-dynamic-linking", + products: [ + // Products can be used to vend plugins, making them visible to other packages. + .plugin( + name: "command-plugin-dynamic-linking", + targets: ["command-plugin-dynamic-linking"]), + ], + dependencies: [ + .package(path: "LocalPackages/DynamicLib") + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "Core", + dependencies: [ + .product(name: "DynamicLib", package: "DynamicLib") + ] + ), + .plugin( + name: "command-plugin-dynamic-linking", + capability: .command(intent: .custom( + verb: "command_plugin_dynamic_linking", + description: "prints hello world" + )), + dependencies: [ + "Core" + ] + ) + ] + ) + """ + ) + try localFileSystem.writeFileContents( + packageDir.appending(components: "Sources", "Core", "Core.swift"), + string: + """ + import DynamicLib + + @main + struct Core { + static func main() { + let result = dynamicLibFunc() + print(result) + } + } + """ + ) + try localFileSystem.writeFileContents( + packageDir.appending(components: "Plugins", "command-plugin-dynamic-linking.swift"), + string: + """ + import PackagePlugin + import Foundation + + enum CommandError: Error, CustomStringConvertible { + var description: String { + String(describing: self) + } + + case pluginError(String) + } + + @main + struct command_plugin_dynamic_linking: CommandPlugin { + // Entry point for command plugins applied to Swift Packages. + func performCommand(context: PluginContext, arguments: [String]) async throws { + let tool = try context.tool(named: "Core") + + let process = try Process.run(tool.url, arguments: arguments) + process.waitUntilExit() + + if process.terminationReason != .exit || process.terminationStatus != 0 { + throw CommandError.pluginError("\\(tool.name) failed") + } else { + print("Works fine!") + } + } + } + + #if canImport(XcodeProjectPlugin) + import XcodeProjectPlugin + + extension command_plugin_dynamic_linking: XcodeCommandPlugin { + // Entry point for command plugins applied to Xcode projects. + func performCommand(context: XcodePluginContext, arguments: [String]) throws { + print("Hello, World!") + } + } + + #endif + """ + ) + + try localFileSystem.writeFileContents( + packageDir.appending(components: "LocalPackages", "DynamicLib", "Package.swift"), + string: + """ + // swift-tools-version: 6.0 + // The swift-tools-version declares the minimum version of Swift required to build this package. + + import PackageDescription + + let package = Package( + name: "DynamicLib", + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "DynamicLib", + type: .dynamic, + targets: ["DynamicLib"]), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "DynamicLib"), + .testTarget( + name: "DynamicLibTests", + dependencies: ["DynamicLib"] + ), + ] + ) + """ + ) + + try localFileSystem.writeFileContents( + packageDir.appending(components: "LocalPackages", "DynamicLib", "Sources", "DynamicLib.swift"), + string: + """ + // The Swift Programming Language + // https://docs.swift.org/swift-book + + public func dynamicLibFunc() -> String { + return "Hello from DynamicLib!" + } + """ + ) + + let (stdout, _) = try await execute( + ["plugin", "command_plugin_dynamic_linking"], + packagePath: packageDir, + configuration: buildData.config, + buildSystem: buildData.buildSystem, + ) + + #expect(stdout.contains("Works fine!")) + } + } } } From 1d7d046b1a8a2e88756bf095c5d674de470325a5 Mon Sep 17 00:00:00 2001 From: Mirza Ucanbarlic <56406159+supersonicbyte@users.noreply.github.com> Date: Sat, 6 Sep 2025 12:02:49 +0200 Subject: [PATCH 3/5] wrap test in withKnownIssue --- Tests/CommandsTests/PackageCommandTests.swift | 284 +++++++++--------- 1 file changed, 144 insertions(+), 140 deletions(-) diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index 02ec2b36eb2..a0c540ca58b 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -6953,166 +6953,170 @@ struct PackageCommandTests { func commandPluginDynamicDependencies( buildData: BuildData ) async throws { - try await testWithTemporaryDirectory { tmpPath in - // Create a sample package with a command plugin that has a dynamic dependency. - let packageDir = tmpPath.appending(components: "MyPackage") - try localFileSystem.writeFileContents( - packageDir.appending(components: "Package.swift"), - string: - """ - // swift-tools-version: 6.0 - // The swift-tools-version declares the minimum version of Swift required to build this package. + try await withKnownIssue { + try await testWithTemporaryDirectory { tmpPath in + // Create a sample package with a command plugin that has a dynamic dependency. + let packageDir = tmpPath.appending(components: "MyPackage") + try localFileSystem.writeFileContents( + packageDir.appending(components: "Package.swift"), + string: + """ + // swift-tools-version: 6.0 + // The swift-tools-version declares the minimum version of Swift required to build this package. - import PackageDescription + import PackageDescription - let package = Package( - name: "command-plugin-dynamic-linking", - products: [ - // Products can be used to vend plugins, making them visible to other packages. - .plugin( - name: "command-plugin-dynamic-linking", - targets: ["command-plugin-dynamic-linking"]), - ], - dependencies: [ - .package(path: "LocalPackages/DynamicLib") - ], - targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. - .executableTarget( - name: "Core", - dependencies: [ - .product(name: "DynamicLib", package: "DynamicLib") - ] - ), - .plugin( - name: "command-plugin-dynamic-linking", - capability: .command(intent: .custom( - verb: "command_plugin_dynamic_linking", - description: "prints hello world" - )), - dependencies: [ - "Core" - ] - ) - ] - ) - """ - ) - try localFileSystem.writeFileContents( - packageDir.appending(components: "Sources", "Core", "Core.swift"), - string: - """ - import DynamicLib + let package = Package( + name: "command-plugin-dynamic-linking", + products: [ + // Products can be used to vend plugins, making them visible to other packages. + .plugin( + name: "command-plugin-dynamic-linking", + targets: ["command-plugin-dynamic-linking"]), + ], + dependencies: [ + .package(path: "LocalPackages/DynamicLib") + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .executableTarget( + name: "Core", + dependencies: [ + .product(name: "DynamicLib", package: "DynamicLib") + ] + ), + .plugin( + name: "command-plugin-dynamic-linking", + capability: .command(intent: .custom( + verb: "command_plugin_dynamic_linking", + description: "prints hello world" + )), + dependencies: [ + "Core" + ] + ) + ] + ) + """ + ) + try localFileSystem.writeFileContents( + packageDir.appending(components: "Sources", "Core", "Core.swift"), + string: + """ + import DynamicLib - @main - struct Core { - static func main() { - let result = dynamicLibFunc() - print(result) + @main + struct Core { + static func main() { + let result = dynamicLibFunc() + print(result) + } } - } - """ - ) - try localFileSystem.writeFileContents( - packageDir.appending(components: "Plugins", "command-plugin-dynamic-linking.swift"), - string: - """ - import PackagePlugin - import Foundation + """ + ) + try localFileSystem.writeFileContents( + packageDir.appending(components: "Plugins", "command-plugin-dynamic-linking.swift"), + string: + """ + import PackagePlugin + import Foundation - enum CommandError: Error, CustomStringConvertible { - var description: String { - String(describing: self) - } + enum CommandError: Error, CustomStringConvertible { + var description: String { + String(describing: self) + } - case pluginError(String) - } + case pluginError(String) + } - @main - struct command_plugin_dynamic_linking: CommandPlugin { - // Entry point for command plugins applied to Swift Packages. - func performCommand(context: PluginContext, arguments: [String]) async throws { - let tool = try context.tool(named: "Core") - - let process = try Process.run(tool.url, arguments: arguments) - process.waitUntilExit() - - if process.terminationReason != .exit || process.terminationStatus != 0 { - throw CommandError.pluginError("\\(tool.name) failed") - } else { - print("Works fine!") + @main + struct command_plugin_dynamic_linking: CommandPlugin { + // Entry point for command plugins applied to Swift Packages. + func performCommand(context: PluginContext, arguments: [String]) async throws { + let tool = try context.tool(named: "Core") + + let process = try Process.run(tool.url, arguments: arguments) + process.waitUntilExit() + + if process.terminationReason != .exit || process.terminationStatus != 0 { + throw CommandError.pluginError("\\(tool.name) failed") + } else { + print("Works fine!") + } } } - } - #if canImport(XcodeProjectPlugin) - import XcodeProjectPlugin + #if canImport(XcodeProjectPlugin) + import XcodeProjectPlugin - extension command_plugin_dynamic_linking: XcodeCommandPlugin { - // Entry point for command plugins applied to Xcode projects. - func performCommand(context: XcodePluginContext, arguments: [String]) throws { - print("Hello, World!") + extension command_plugin_dynamic_linking: XcodeCommandPlugin { + // Entry point for command plugins applied to Xcode projects. + func performCommand(context: XcodePluginContext, arguments: [String]) throws { + print("Hello, World!") + } } - } - #endif - """ - ) + #endif + """ + ) - try localFileSystem.writeFileContents( - packageDir.appending(components: "LocalPackages", "DynamicLib", "Package.swift"), - string: - """ - // swift-tools-version: 6.0 - // The swift-tools-version declares the minimum version of Swift required to build this package. + try localFileSystem.writeFileContents( + packageDir.appending(components: "LocalPackages", "DynamicLib", "Package.swift"), + string: + """ + // swift-tools-version: 6.0 + // The swift-tools-version declares the minimum version of Swift required to build this package. - import PackageDescription + import PackageDescription - let package = Package( - name: "DynamicLib", - products: [ - // Products define the executables and libraries a package produces, making them visible to other packages. - .library( - name: "DynamicLib", - type: .dynamic, - targets: ["DynamicLib"]), - ], - targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. - .target( - name: "DynamicLib"), - .testTarget( - name: "DynamicLibTests", - dependencies: ["DynamicLib"] - ), - ] - ) - """ - ) + let package = Package( + name: "DynamicLib", + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "DynamicLib", + type: .dynamic, + targets: ["DynamicLib"]), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "DynamicLib"), + .testTarget( + name: "DynamicLibTests", + dependencies: ["DynamicLib"] + ), + ] + ) + """ + ) - try localFileSystem.writeFileContents( - packageDir.appending(components: "LocalPackages", "DynamicLib", "Sources", "DynamicLib.swift"), - string: - """ - // The Swift Programming Language - // https://docs.swift.org/swift-book + try localFileSystem.writeFileContents( + packageDir.appending(components: "LocalPackages", "DynamicLib", "Sources", "DynamicLib.swift"), + string: + """ + // The Swift Programming Language + // https://docs.swift.org/swift-book - public func dynamicLibFunc() -> String { - return "Hello from DynamicLib!" - } - """ - ) + public func dynamicLibFunc() -> String { + return "Hello from DynamicLib!" + } + """ + ) - let (stdout, _) = try await execute( - ["plugin", "command_plugin_dynamic_linking"], - packagePath: packageDir, - configuration: buildData.config, - buildSystem: buildData.buildSystem, - ) + let (stdout, _) = try await execute( + ["plugin", "command_plugin_dynamic_linking"], + packagePath: packageDir, + configuration: buildData.config, + buildSystem: buildData.buildSystem, + ) - #expect(stdout.contains("Works fine!")) + #expect(stdout.contains("Works fine!")) + } + } when: { + ProcessInfo.hostOperatingSystem == .windows || (ProcessInfo.hostOperatingSystem == .linux && buildData.buildSystem == .swiftbuild) } } } From 8c9e3b360121b39df00020c3176635849bd6ae13 Mon Sep 17 00:00:00 2001 From: Mirza Ucanbarlic <56406159+supersonicbyte@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:59:34 +0200 Subject: [PATCH 4/5] remove windows from known issue --- Tests/CommandsTests/PackageCommandTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index a0c540ca58b..674377079f1 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -7116,7 +7116,7 @@ struct PackageCommandTests { #expect(stdout.contains("Works fine!")) } } when: { - ProcessInfo.hostOperatingSystem == .windows || (ProcessInfo.hostOperatingSystem == .linux && buildData.buildSystem == .swiftbuild) + (ProcessInfo.hostOperatingSystem == .linux && buildData.buildSystem == .swiftbuild) } } } From 32701fbda7c9670040c8e082985c0dce7af71c1e Mon Sep 17 00:00:00 2001 From: Mirza Ucanbarlic <56406159+supersonicbyte@users.noreply.github.com> Date: Mon, 15 Sep 2025 09:50:36 +0200 Subject: [PATCH 5/5] fix the when condition --- Tests/CommandsTests/PackageCommandTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index 674377079f1..3b8a6156adb 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -7116,7 +7116,7 @@ struct PackageCommandTests { #expect(stdout.contains("Works fine!")) } } when: { - (ProcessInfo.hostOperatingSystem == .linux && buildData.buildSystem == .swiftbuild) + (ProcessInfo.hostOperatingSystem == .windows && buildData.buildSystem == .swiftbuild) || (ProcessInfo.hostOperatingSystem == .linux && buildData.buildSystem == .swiftbuild) } } }