Adding package dependency to Macro is throwing "Failed to receive result from plugin" error

I am in the process of creating a macro to generate random sample data for unit testing and SwiftUI previews. To produce this data, I am utilizing a library named Fakery (https://github.com/vadymmarkov/Fakery). Everything seemed to be compiling and functioning smoothly until I initiated a call to an object from Fakery. At this juncture, Xcode presented me with an error stating, "Failed to receive result from plugin", indicating a problem with code generation.

In my efforts to pinpoint the issue, I created a fresh project to modify the #stringify macro so it would produce a String utilizing Fakery. The identical issue reemerged. Out of curiosity, I introduced alternative dependencies with the capability of generating Strings for testing. To my astonishment, these other libraries did not manifest the same problem that Fakery was producing.

The puzzling aspect is that the tests run and pass flawlessly, and everything compiles perfectly unless the client invokes these particular instances. This leads me to wonder: Are there specific requirements for dependencies in macros that ensure proper code generation and correct compilation?

Below is the package configuration, with the submodule utilizing Fakery being dubbed "Demo":

import PackageDescription
import CompilerPluginSupport

let package = Package(
    name: "MyMacroDemoFakery",
    platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
    products: [
        // Products define the executables and libraries a package produces, making them visible to other packages.
        .library(
            name: "MyMacroDemoFakery",
            targets: ["MyMacroDemoFakery"]
        ),
        .executable(
            name: "MyMacroDemoFakeryClient",
            targets: ["MyMacroDemoFakeryClient"]
        ),
    ],
    dependencies: [
        // Depend on the latest Swift 5.9 prerelease of SwiftSyntax
        .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0-swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-25-b"),
        .package(url: "https://github.com/vadymmarkov/Fakery.git", from: "5.1.0"),
        .package(url: "https://github.com/Rightpoint/BonMot.git", from: "6.1.3")
    ],
    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.
        // Macro implementation that performs the source transformation of a macro.
        .macro(
            name: "MyMacroDemoFakeryMacros",
            dependencies: [
                .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
                .product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
                "Demo",
            ]
        ),

        // Library that exposes a macro as part of its API, which is used in client programs.
        .target(name: "MyMacroDemoFakery", dependencies: ["MyMacroDemoFakeryMacros"]),
        
        .target(name: "Demo", dependencies: [
            .product(name: "Fakery", package: "Fakery"), // Fakery is failing 🔥
            .product(name: "BonMot", package: "BonMot") // BonMot is working as expected âś…
        ]),

        // A client of the library, which is able to use the macro in its own code.
        .executableTarget(name: "MyMacroDemoFakeryClient", dependencies: [
            "MyMacroDemoFakery",
        ]),

        // A test target used to develop the macro implementation.
        .testTarget(
            name: "MyMacroDemoFakeryTests",
            dependencies: [
                "MyMacroDemoFakeryMacros",
                .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
                "Demo"
            ]
        ),
    ]
)

This is the code incorporating Fakery:

import Foundation
import Fakery
import BonMot

public struct Demo {
    public static func getName() -> String {
        Faker().name.name()
        //"\"\(StringStyle().attributes.description)\""//#""Hello""#
    }
}

The Macro implementation:

public struct StringifyMacro: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) -> ExprSyntax {
        guard let argument = node.argumentList.first?.expression else {
            fatalError("compiler bug: the macro does not have any arguments")
        }

        return ExprSyntax(stringLiteral: Demo.getName())
    }
}

And here is the client:

import MyMacroDemoFakery

let result = #stringify(1) 

print("The value \(result)")

Thanks,
Pitt

Could you share your Stringify project?

Hi John, Here's the link https://github.com/pitt500/MyMacroDemoFakery

Let me know if you have any questions.

Thanks!

I think Fakery_Fakery.bundle can't be accessed by your macro subprocess causing a crash. This can be verified by checking for Bundle(for: Faker.self) directly in your Demo.getName().

I built a similar project to directly access a file which, even using swift build --disable-sandbox, resulted in:

The value The file “messages.txt” couldn’t be opened because you don’t have permission to view it.

I don't know if this is a deliberate limitation or a bug in swiftc. TBH the fact that I had to spend over an hour tracking this down shows we probably need a better way of getting information out of the macro process, ie allow logging to the console.

1 Like

Thanks for looking into this, John. Upon further investigation of the Fakery library, I've observed that it reads files to fetch data. However, as far as I understand, macros don't permit this action, which might be the root of our problem. As you pointed out, the compiler isn't shedding much light on the matter, making it challenging to diagnose immediately.

The main concern arises when attempting to retrieve the actual string from Fakery within the macro. I've discovered a workaround: have Demo.getName() return a literal string "Faker().name.name()", and then incorporate the Fakery dependencies in the client.

I believe macros are sandboxed in a way that they cannot access the file system at all. It does seem like an issue that it isn't more obvious that this is what's happening though.

2 Likes