How to depend on a non-macOS external package in macros

Hello everyone,
I am trying to create a Macro that depends on an external package to abstract some of the packages logic. The point is that the dependency does not support macOS which results in a build failure when trying to build the macro.

Since macOS 10.15 is required for creating macros, any thing can be done to get it to work?

Here is a sample to what I'm trying to do

import PackageDescription
import CompilerPluginSupport

let package = Package(
    name: "Stringify",
    platforms: [
        .macOS(.v10_15),
        .iOS(.v13),
        .tvOS(.v13),
        .watchOS(.v6),
        .macCatalyst(.v13)
    ],
    products: [
        .library(
            name: "Stringify",
            targets: ["Stringify"]
        ),
        .executable(
            name: "StringifyClient",
            targets: ["StringifyClient"]
        ),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"),
        .package(url: "https://github.com/MyDependency", from: "1.0.0"),
    ],
    targets: [
        .target(
            name: "Stringify",
            dependencies: ["StringifyMacros"]
        ),
        .executableTarget(
            name: "StringifyClient",
            dependencies: ["Stringify"]
        ),
        .macro(
            name: "StringifyMacros",
            dependencies: [
                .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
                .product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
                .product(name: "MyDependency", package: "MyDependency"),
            ],
                swiftSettings: [
                    .define("IOS_ONLY")
                ]
        ),
    ],
    swiftLanguageVersions: [.v5]
)

Hi there,
Welcome to Swift Forums @ahmedsalah!

May I ask what kind of code you want to use in your macro implementation from your MyDependency package?

I can't think of any good reason for a macro implementation (not the macro interface) to depend on an external package that doesn't support macOS or Linux.

Could you provide more details about why you need this dependency in your macro? There might be alternative approaches we could explore.

Hi @Matejkob

It's still a long shot but we're a 3rd party package trying to extend SwiftUI views behavior to contain some extra functionalities/data by appending custom modifiers.

The modifiers themselves are defined in the 3rd party library s, what I understand is that for us to use them, we'll need to depend the 3rd party package, that's correct?

Swift macros transform your source code during compilation. They operate on the syntax level, working with the Abstract Syntax Tree of your code They do NOT resone about type system. Here's the key point: in the macro implementation, you can generate code that references any type or function. This is because the macro is essentially generating "plain text" (it's simplification) that gets inserted into your source code.

The macro expansion happens at compilation time, and it's only at the point in your source code where you've used the macro that you need to provide knowledge about types. This means you can "use" your third-party SwiftUI modifiers in your macro implementation-but not as a type type but as a function identifier.

Here's how this could work for your use case:

  1. In your macro implementation, you can generate code that references your third-party SwiftUI modifiers, even though the macro implementation itself doesn't have access to those types.

  2. The macro declaration and implementation have be in a separate targets that doesn't depend on your third-party library.

  3. In your main target, where you actually use the macro, you'll need to import both your macro module and the third-party library with the SwiftUI modifiers.

Here's a simplified example:

// In your macro implementation (target of a type `macro`)
public enum CustomModifierMacro: SomeMacroProtocol {
    public static func expansion(...) throws -> [DeclSyntax] {
        // Generate code referencing third-party modifiers
        return [
            "func customModifier() -> some View { self.modifier(ThirdPartyLibrary.CustomViewModifier()) }"
        ]
    }
}

// In your main Swift file
import YourMacroModule
import ThirdPartyLibrary

@CustomModifier
struct MyView: View {
    var body: some View {
        Text("Hello, World!")
    }
}

In this setup, the macro generates code that references ThirdPartyLibrary.CustomViewModifier(), even though the macro implementation doesn't have direct access to this type. It's only when this generated code is inserted into your main source file (where you've used the @CustomModifier attribute) that the compiler needs to resolve these types.

2 Likes

Thank you for the swift and insightful answer :pray: