Swift’s permissive nature when it comes to module dependencies has always annoyed me and made my life a bit harder than I expected. Of course, I’m talking about transitive module imports.

You know the situation: there is a ModuleA which is a dependency of ModuleB, and if you declare ModuleB as a dependency of ModuleC, ModuleC can import ModuleA even though it never declared ModuleA as its own direct dependency.

There are a lot of reasons why you might not want that, from making dependencies harder to visualize to enforcing a certain module structure, and so on.

Unfortunately, Swift itself does not provide a mechanism to enforce direct module dependencies—or at least I don’t know of one.

Layering check via rules_swift

One of the reasons I like working with Bazel is the flexibility it provides. One example of that is a feature in rules_swift called layering check, which prevents importing modules that aren’t declared as direct dependencies.

It was introduced a couple of months ago in #1780.

So, for example:

swift_library(
    name = "Bottom",
    srcs = ["Bottom.swift"],
    module_name = "Bottom",
)

swift_library(
    name = "Middle",
    srcs = ["Middle.swift"],
    module_name = "Middle",
    deps = [":Bottom"],
)

swift_library(
    name = "Top",
    srcs = ["Top.swift"],
    module_name = "Top",
    deps = [":Middle"],
)

Now let’s say Top.swift tries to do this:

import Middle
import Bottom

Even though Bottom is available transitively through Middle, Top never declared it as a direct dependency.

With the Swift layering check enabled, building it:

bazel build //:Top --features=swift.layering_check_swift

should fail with an error resembling:

Layering violation in //path/to/package:Top
  The following modules were imported, but they are not direct dependencies:

      Bottom

  Please add the correct 'deps' ...

And that’s exactly what I want.

To fix it, simply declare Bottom as a direct Bazel dependency:

swift_library(
    name = "Top",
    srcs = ["Top.swift"],
    module_name = "Top",
    deps = [
        ":Bottom",
        ":Middle",
    ],
)

Conclusion

This is one of those features I wish more people knew about and used. It gives you much tighter control over your dependency graph and makes it harder for accidental dependencies to get imported.