`Swift build` vs `swift run` build dependencies differently?!

Ok, now I am a bit confused, I get different dependencies built depending on if I use build or run from clean slate.

If I do swift build -c release I will get the MyDynamicPlugin dylib properly generated in the build directory, but if I from clean slate do swift run -c release it is NOT built, even if listed as a dependency for the HostingApp (so no dylib is generated then).

Shouldn't these two commands build the same set of dependencies, or what am I missing in the Swift.package setup?

Any pointers appreciated.

// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "HostingApp",
    platforms: [
        .macOS(.v12),
    ],
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .executable(
            name: "HostingApp",
            targets: ["HostingApp"]
        ),
        .library(
            name: "MyDynamicPlugin",
            type: .dynamic,
            targets: ["MyDynamicPlugin"]),
        .library(
            name: "MyPluginAPI",
            targets: ["MyPluginAPI"]
        ),
        .library(
            name: "HostingAppAPI",
            targets: ["HostingAppAPI"]
        ),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "https://github.com/redacted-organization/latency-tools", branch: "hassila-performance"),
        .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "1.0.0-alpha"),
        .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.1.0")),
        .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.0.0")),
        .package(url: "https://github.com/redacted-organization/swift-plugin.git", .upToNextMajor(from: "0.0.1")),
        .package(url: "https://github.com/redacted-organization/swift-plugin-manager.git", branch: "main") // must depend on branch
        // as unsafe flags are used by dependency https://github.com/apple/swift-package-manager/pull/2254
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .executableTarget(
            name: "HostingApp",
            dependencies: [
                .product(name: "LatencyStatistics", package: "latency-tools"),
                .product(name: "LatencyTimer", package: "latency-tools"),
                .product(name: "Lifecycle", package: "swift-service-lifecycle"),
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
                .product(name: "PluginManager", package: "swift-plugin-manager"),
                "MyPluginAPI",
                "HostingAppAPI",
                "MyDynamicPlugin"
            ]
        ),
        .target(name: "MyDynamicPlugin",
                dependencies: [
                    .product(name: "LatencyStatistics", package: "latency-tools"),
                    .product(name: "LatencyTimer", package: "latency-tools"),
                    .product(name: "Logging", package: "swift-log"),
                    "MyPluginAPI",
                    "HostingAppAPI"
                ]
        ),
        .target(name: "HostingAppAPI", dependencies: [
        ]),
        .target(name: "MyPluginAPI", dependencies: [
            .product(name: "Logging", package: "swift-log"),
            .product(name: "Plugin", package: "swift-plugin"),
            "HostingAppAPI"
        ])
    ]
)

swift build builds all products. swift run is short for swift run HostingApp (and only works that way because there is only one executable). It only builds what is needed for HostingApp. It is analogous to swift build --product MyDynamicPlugin, but also launches the executable when it is finished.

Your HostingApp executable target is depending on the MyDynamicPlugin target and linking it statically. That is why the dynamic library product is not formally required by the executable. There is no way make a target depend on a product from the same package. If that is what you need, you will have to move the dynamic library into a dependency package. (Yes, I too wish it were possible.)

2 Likes

Many thanks Jeremy, quite non-intuitive and just a bit unexpected.

In reality the dynamic libraries that will be loaded by the hosting app will be separate packages and built independently, but this was a prototype just to test workflow and then it was very convenient to have it all local just for testing.

It definitely would be nice to be able to depend on products from the same package - overall, I must say the product/target split does not always leave my brain in a good state... (seem to remember there was some discussion to revamp/simplify that, but lost the reference to it)