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 { diff --git a/Tests/CommandsTests/PackageCommandTests.swift b/Tests/CommandsTests/PackageCommandTests.swift index 0c6cf09e023..a0c540ca58b 100644 --- a/Tests/CommandsTests/PackageCommandTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -6948,5 +6948,176 @@ struct PackageCommandTests { expectNoDiagnostics(observability.diagnostics) } } + + @Test(arguments: getBuildData(for: SupportedBuildSystemOnAllPlatforms)) + func commandPluginDynamicDependencies( + buildData: BuildData + ) async throws { + 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 + + 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!")) + } + } when: { + ProcessInfo.hostOperatingSystem == .windows || (ProcessInfo.hostOperatingSystem == .linux && buildData.buildSystem == .swiftbuild) + } + } } }