SwiftPM + canImport

Hey guys!

I was very excited by the time canImport landed, it’s a great feature, but it doesn’t help much in SwiftPM environment.

Here’s an example:

let package = Package(
    name: "X",
    dependencies: [
        .package(url: "file.git", .branch("master")),
        .package(url: "stream.git", .branch("master")),
        .package(url: "crypto.git", .branch("master"))
    ],
    ...
)

It would be great to avoid heavy crypto package or even a simple stream if we only need file.git, but if they’re available somewhere in the graph we would get extra APIs from the file.git:

file+extensions.swift:

@if canImport(Stream) {
    import Stream

    extension File {
        public func open() -> Stream { ... }
    }
}

@if canImport(Crypto) {
    import Crypto

    extension File {
        public func sha() -> Sha { ... }
    }
}

I think the easier way to help the build system is to add optionalDependencies: to the .target:

let package = Package(
    name: "File",
    ...
    optionalDependencies: [
        .package(url: "stream.git", .branch("master")),
        .package(url: "crypto.git", .branch("master"))
    ],
    targets: [
        .target(
            name: "File",
            dependencies: [],
            optionalDependencies: ["Stream", "Crypto"])
    ]
)
3 Likes

I think adding optional dependencies is an excellent idea and I wholeheartedly support it!

I think one major question is how will the package manager decide if an optional dependency should be cloned or not.

By default it shouldn’t. It should be used as dependency only if it’s available somewhere in the graph (if it’s being used as non-optional dependency somewhere)

I see, that makes sense I think. There are several other open questions in my mind, for e.g.:

  • What happens if there is a non-optional dependency in the graph but they’re not satisfiable?
  • What happenes when a package with optional dependencies is a root package?
  • Optional dependencies can also unnecessarily constrain a particular dependency in the graph. Maybe this isn’t an actual issue though.

There are probably more open questions that I haven’t thought of yet. Feel free to work on a draft proposal to flesh out the details!

The answer to all of this: nothing happens. It doesn’t add any constrain. We only use the dependency if we can satisfy the requirements using the main graph.

Well, actually we could treat them as non-optional in this case because it’s a dev-only story I think.

Will do.

Is this possible nowadays?

I'm trying to conditionally add code into a SPM target (A) only when that's a dependency of another target (B) that imports another dependency (C).

in target A
#if canImport(C)
...

target B imports A and C.

I thought canImport would work for this but it doesn't seem to behave as I expect, at least from Xcode. Sometimes the code is compiled when compiling the schema for A, even tho C is not imported at all. Other times compiling B doesn't work, even tho it imports both other targets.

I don't think canImport works for this, since it is based on whether or not there happens to be a module C in the search path. If you don't explicitly depend on it, that will not be deterministic as C can be build in parallel or even after A. It can also be present from previous builds if you are building incrementally.

1 Like

That’s what it looked like ^^ thanks for confirming it!

I guess there is no way to get something like this then?

Basically I wish I could do the same Apple does where you need to import SwiftUI and MapKit to get access to map view, that but with my own (or 3rd party) packages.

This is because otherwise you always need to make an extra package that brings both together. This is fine for 1 but when you modularise your app things like this arise a bunch.

Sounds like you want cross-import overlays, which have been pitched but are not a finished, publicly available feature:

Oh that seems to be what I was looking for. In my mind the can import technique makes more sense so you don’t have to make more packages but maybe it can’t work. Anyway I will look into that other thread.
cheers

Terms of Service

Privacy Policy

Cookie Policy