[Pitch] SwiftPM: Allow targets to depend on products in the same package

I have implemented it as .product(name:), so referencing products from the same package requires not passing a package name.

Now I wonder about moduleAliases and condition. Currently I pass both through, but I started testing and realized they don’t work (at least moduleAliases, haven’t checked condition yet).

It may be possible to get both to work, now I am wondering whether they are needed.

Pro: It may simplify local package refactorings like merging two packages into one, if those are using moduleAliases and/or condition.

Con: I wonder whether this is needed in the same package, if not I would just add unneeded extra complexity.

I would love to hear opinions on this! :slight_smile:

… I missed something: moduleAliases won't help when merging packages as we would end up with targets having the same name :person_facepalming: . I'll remove it for now, and will look into condition next.

Are there any updates on this pitch? I really really want to be able to use this feature in one of my projects with certain dynamic library requirements which have led to so many headaches with nested packages and duplicated code warnings. I'd be happy to help out with the implementation side of things if that's the blocker.

Hi @stackotter, I did not have much time to work on this. The proof-of-concept implementation is there, for production quality it needs test coverage (I'd rather check it works now as a proof of concept, and polish it later if the evolution proposal gets accepted).

Next up is to write the actual evolution proposal, I'll see what I can do until next weekend. As far as I understood this needs to go over Swift evolution, as it changes the format of the package manifest.

1 Like

Ok sounds good, let me know if there's anything I can do to help :)

Hey @objc I have time to start on an evolution proposal, and can still see this being incredibly useful for me (my current project structure gets on my nerves and when I tested out your proof-of-concept implementation it instantly fixed everything!) and anyone else trying to create cross-platform apps with SwiftPM. Would you like me to make a start on the evolution proposal?

Sure, please go ahead! As you may have noticed I don’t find the time :frowning:

1 Like

This existing Target.Dependency.product overload allows for the package parameter to be omitted (defaulting it to nil), which presumably means that SwiftPM will search for a product of the given name in a package of the same name. This is at odds with adding a .product(name:) overload for referring to products within the current package (which would introduce ambiguity).

I propose that we should instead allow the existing overload to refer to packages within the current package using the existing rules (if the product name matches the package name then the package name can be omitted) and introduce an overload of a different name specifically as a shorthand for products within the current package, such as .internalProduct(name:moduleAliases:condition:) (moduleAliases is an existing parameter accepted last year).

How do people feel about these changes?


As an aside, should .internalProduct(name:moduleAliases:condition:) include the moduleAliases parameter, or would that be a bit counterproductive? It's my understanding that the moduleAliases parameter was introduced so that naming clashes can be resolved without making changes to upstream packages. However, in this case the product is within the current package by definition, so such naming clashes should be easy to fix (if not by simply using moduleAliases on the offending external product instead, since clashing targets come in pairs anyway).

Using .product(name:) does fail at the moment with an error message like "unknown package name 'unknown package name' in dependencies of target 'pkgtest'; valid packages are: […]"

So I think it should be fine to use it to reference products in the same package in the future.

In my old branch I omitted moduleAliases. I think that should be fine as it was introduced to prevent name collisions when using packages from others.

1 Like

Hm yeah I just tried it out and had the same issue, any idea why the package parameter is optional? It looks like the package parameter became required in Swift 5.2, and then the moduleAliases proposal randomly made their package parameter optional. Unfortunately the moduleAliasing proposal doesn't detail why. It weirdly doesn't even contain the API for the methods they intended to introduce. It kinda seems like a mistake to me (they copied the obsoleted API directly above the method). The other overload they created is correct.


EDIT: I've opened an issue on the swift-package-manager repository since there were quite a few signs that the overload was unintentionally added by SE0339.

Should the existing .product(name:package:moduleAliases:) method allow the package to be the current package, or would this be a bit weird since then you'd still be able to use moduleAliases for products within the current package?

wouldn’t this cause the behavior to change depending on the name of the directory containing the project?

2 Likes

Ah very good point, that settles it then. Unless we make an exception for the current package and use the name provided in the package manifest, but I don’t think that’d be worthwhile given there’s really no point to specify the package name anyway (in the case of being within the same package).

1 Like

It could well be the overload you describe was added by accident.

Whatever the reason, .product(name:) should be free as a syntax to reference a product from the same package :slight_smile:

I just opened a draft PR for the proposal :tada:: Package Manager Allow Targets to Depend on Products in the Same Package by stackotter · Pull Request #2234 · apple/swift-evolution · GitHub

It's starting to come together and I'd love to hear any feedback anyone has for what I've got so far!

2 Likes

I've now got a basic implementation that's up-to-date with swift-package-manager/main with a draft PR here. Still need to add cycle checking and tests, but at least people can test it out with the latest Swift features now. I'll aim to get it ready for review in the coming weeks :crossed_fingers:

4 Likes

We're building some cross-platform software that would benefit greatly from this scenario as well.

Working with Node.js requires creating a native dynamic library that is renamed to MyLibrary.cjs.node in the package (e.g. MyLibrary.dll, libMyLibrary.dylib, and libMyLibrary.so all need to be renamed). This is fine in the case of including one native dynamic library, but confuses OS dynamic library loaders in the case that several dependencies need to load the same library.

The solution is to build a small shim dynamic library that performs Node.js initialization and then loads the actual dependency. Product dependencies are perfect for this, but currently require creating an entirely different Package.swift and referencing externally.

@stackotter I'd be happy to test this in my real-world scenario!