Multiple Target Issue with SPM

Hi all. I am having an issue with package dependencies and multiple targets names. Coincidently it is the SwiftPM package that I have a dependency on and having the multiple target name issue. SwiftPM package description declares two separate libraries. One is SwiftPM and the other is Utility. The SwiftPM library has a target dependent on "SourceControl". My project has a dependency on the SwiftPM package but only through the Utility library. When I add a target to my package, also titled "SourceControl" I get the error "error: multiple targets named 'SourceControl' in: SourceCheck, SwiftPM".

I am not using the SourceCheck target found in SwiftPM library.

How can I go about resolving this error? I could rename my target but I would prefer not to as I don't feel like being creative enough to invent a new name for "SourceControl". Is it possible to instruct the package manager to not import the library targets of a library I am not using? Is there a way to simulate "name spacing" when importing libraries and targets? I was secretly hoping that my local target would get first priority before external targets when the package was being built. Thanks again for such a great manager!

1 Like

In theory, we can have SwiftPM only load the targets which are actively used in the graph but I don't think we should do that. It'll make it easy for users to run into these kinds of issues when they try to use a different product of a dependency. That said, we can rename SwiftPM's targets and add a prefix to avoid such collisions. We've already renamed one of target from libc to SPMLibc.

1 Like

@aciid, thanks for getting back to me. What would be the expected behavior when I have a dependency on two different libraries with the same targets? Would I just not be able to depend on both of them?

For now, it is not possible to depend on two packages that share a target name. We might be able to do something smart like automatic module renaming but it is a complex problem. There are also runtime implications (for e.g. NSClassFromString() might break).

@aciid Got ya. A solution for this would be good. Do you know of any active work or discussion on this? I would be interested in helping. Thanks again for getting back.

+1

The SwiftPM package was the first place I encountered name collisions and since then I have prefixed all of my own module names to avoid this same problem.

Once we have Target-Based Dependency Resolution, I wonder if we should allow multiple targets with the same name in your package graph so long as they're not both in your target dependency graph.

A good reason to allow this is for the "test-only dependencies" use case. If a package has a target only used for its own development, which clients will not build, it's less likely that the package's author may have bothered to pick a very unique name. That means that there's a good chance it'll conflict with some other target given a big enough package graph. Since the client won't actually build these targets, it might be better to allow this case.

That sounds reasonable. I don't think we even need to wait for Target-Based Dependency Resolution for that. We can allow the targets that are not part of any declared product.

This is less of a Swift PM issue and more of a "how modules are designed in Swift" issue. Unlike Java packages and similar modular concepts that define best practices like reverse-DNS form, Swift's design encourages short module names, and this makes it very easy for someone to define modules like "Basic" or "Utility" in isolation, making it easy to collide with modules in other projects once they start integrating other dependencies that they don't own.

At a minimum, we should be offering best practices for naming modules in a scalable way, especially if tools like SPM encourage you to break up your project into multiple small modules. For example, should everyone just prepend their project name to each of their modules for an extra layer of protection? That seems to be where SPM and the common-tools library that's being split out of it are headed. It's an improvement, but it feels like we've just taken the Objective-C prefix problem and moved it one layer up the chain :slightly_frowning_face:

At Google, our Bazel build rules for Swift by default derive a module name from the path to the build target; for example, if the build manifest file foo/bar/baz/BUILD defines a Swift target named quux, then the module name is foo_bar_baz_quux (since we're limited to identifier-valid characters). We mandate the use of auto-generated module names (except for third-party code, which needs to be built with the module name it was designed for), and since we have a monorepo, this is enough to more-or-less guarantee uniqueness. But it leaves us with some very ugly import statements.

I'm not sure what I'd recommend to improve the situation. I could say "let people use dots in module names and encourage reverse-DNS as a best practice", but maybe that's just a "feel good" solution since it's just trading one punctuation mark for another; I can say it looks better, but unless I have something more objective, it's not very convincing.

A bolder idea that @jrose and I discussed briefly once was to extend the private-discriminator concept for fileprivate declarations to whole modules; the idea being that when you build the graph, you can specify unique discriminators for each module at compile time that would affect type lookup and also get encoded into the mangled symbol names (to avoid potential collisions between symbols in the object files). But that really only works if you're building all your dependencies from source, and it has its own complications (changing the mangled symbol names means you need to figure out how to handle runtime type lookup, etc.). :man_shrugging:

3 Likes

@allevato Thanks for responding. I am not sure we should have naming conventions for what people should name their modules. This was the case in objective-c and I have seen such convoluted names for libraries and methods just so the developer could integrate a library with the same names. I personally do not like being limited in naming the modules in my package just because there is another module in another library with the same name. I should be able to express my names as best I can to match my problem domain and give me better understanding of what the module contains. Given a module "Utility" in another package and I have a module also named "Utility", I have an issue with the fact that I now have to rename my module to "MyUtility" or "PackageNameUtility" even though I develop on this software on a daily basis.

I find in Swift that it allows me to express my terms in my domain with very little limitation. We can extend existing types, removing the need for the convoluted naming in languages like Java; we can use Type aliases to rename types so that it matches our problem domain and we can even backtick variable names if there is a collision with a language keyword. I do not see why we should be forced into naming conventions that go against our domain just because of a possibility of a conflict when somebody links against us.

Having a unique global naming system that we hope does not have any collisions with other modules is not sustaining. There are solutions out there as you pointed out, it is just a matter of finding one that is easy to use and makes it useful for developers. The idea that I may not be able to import a module just because another module I use has the same module doesn't feel like a good solution to me. I have this issue with two modules and I am thinking of writing a script that just clones and copies over the source code so that I can keep in my problem domain and this bums me out.

I remember in the wee days of Swift there was this concept of implicit namespaces. I really liked this idea. From what I understood was that if I had a module named "Utility" and I imported "Utility", it would use my "Utility" if I had one. If I did not have a module named "Utility", it would check all imported modules. If none of them had a "Utility" module, then error. If there was one "Utility" module, it would use that one. If there was a conflict of "Utility" modules, then I could choose the one I wanted. In our context, something like "import SwiftPM.Utility". Which would also work if I wanted that "Utility" module in the presence of my module "Utility". If my file imported both, then I would have to be explicit on the types that I chose "let version: SwiftPM.Utility.Version". I really, really like this idea and it would allow me flexibility to resolve conflicts.

In the case of Swift Packages, I could see something down the lines of "Name.Module" as the implicit namespace. Or, even tossing this out there (and I know there was a definite no on this one), "namespace SwiftPM { // code }".

Sorry, such a brain dump. I just feel that this goes against the spirit of the Swift eco-system where we can easily define things in our own terms and I would love to see a more elegant solution.

3 Likes

I'm not sure if this was resolved in the mean time, but even worse than two targets being named the same, even test targets (which are not really relevant when including the libraries) which are named the same (e.g. Tests in two different projects) can lead to conflicts: Hence, I just got this bug report.

Is there any work being done to sort this issue out? Or bug on JIRA that tracks the progress?

No progress yet. The duplicate test-target issue should somewhat resolve itself once target-based dependency resolution is implemented.

2 Likes

So basically, we need to wait until SE-226 is implemented, right? Looking at the current progress in SR-8658 it isn't clear when this is planned for release, would be great to get this with the next Swift release though (5.1), is there a milestone with a list of JIRA tickets planned for the next release?

Apparently (please correct me if I got this wrong), a new Swift runtime was released in macOS Catalina, independently from the Swift toolchain version, that includes a TestSupport target.

We had an issue because we also had a module named TestSupport and everything worked fine for most of us except for the one coworker who had updated to Catalina already. In that case, the package manager would complain because of a duplicate definition of TestSupport. That proved quite difficult to debug because obviously we were all running on the same Swift version.

Because of that, I think it would be really useful to rekindle this discussion and find a solution for such problems.