Copying resources from a symlinked folder while preserving the folder structure

Dear esteemed gentlepeople,

I have a Swift Package with a test target. The test target needs to include a folder as a resource while preserving its folder structure. I have symlinked that folder because it lives in an npm package and contains many large-ish files that are shared with other test suites in other programming languages.

So my structure looks like this:

  • Package.swift
  • node_modules/some-package/Files
  • Swift/Tests/Files (which is a symlink to "../../node_modules/some-package/Files")

My Package.swift:

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

import PackageDescription
import Foundation

let package = Package(
    name: "MyGreatProject",
    platforms: [.iOS(.v15), .macOS(.v13)],
    products: [
        .library(
            name: "MyGreatProject",
            targets: ["MyGreatProject"]),
    ],
    targets: [
        .target(
            name: "MyGreatProject",
            path: "Swift/Sources"
        ),
        .testTarget(
            name: "MyGreatProjectTests",
            dependencies: [
                "MyGreatProject",
            ],
            path: "Swift/Tests",
            resources: [
                // symlinked from node_modules/some-package/Files to Swift/Tests/Files
                .copy("Files"),
            ]
        )
    ]
)

When I open this Swift package in Xcode, I can run the tests and everything works. The symlinked "Files" folder is copied to [Derived Data]/some extra folders/MyGreatProject_MyGreatProjectTests.bundle/Contents/Resources/Files.

However, when I run swift test from the command line, the symlink itself is copied, not its recursive contents. So it creates a symlink named "Files" in .build/arm64-apple-macosx/debug/MyGreatProject_MyGreatProjectTests.bundle which points to "../../node_modules/some-package/Files", which is an invalid path from that location.

Is this a bug? If not, is there a way I can solve this so it works both in Xcode and when running swift test from the command line? Thank you so much!

I would basically always consider it a bug if Xcode and CLI SwiftPM behave differently in a substantial way like this.

For this particular one, it isn't actually clear to me what we would consider the correct behavior. SE-0271 doesn't seem to talk about symbolic links at all, so I don't think anyone has ever really thought about it. The Xcode behavior seems generally more useful to me though, it could see the possibility of wanting to copy a symlink, but it seems rare. There's also the question what should happen if there's a symlink somewhere inside the copied folder structure that points outside of the target.

I don't have any suggestions for a workaround, unfortunately.

@NeoNacho Thank you for your feedback. I agree that this feels like a bug. Good questions about how symlinks should be treated: Since we are talking about copying resources, I would argue that following the top-level symlink would make the most sense. If one of its subdirectories also contains a symlink, it would just be copied as-is. Basically I'd expect the same behavior as cp -r origin_symlink_folder destination_folder: That results in a new folder named destination_folder being created containing the contents of the target of origin_symlink_folder symlink.

I worked around my issue by defining a postinstall script in my package.json that actually copies the folders from the npm package to Swift/Tests/Files.