Modules vs Packages in SwiftPM

I watched the swift tour from WWDC 24 and they explained that a package consists of modules and you can use package access control to access code between modules, vs. internal which makes the code only accessed through the same module only.

And this made me wonder what is a module and which part in the SPM is a module, and how can I create a new module in side of a package ? Can anybody please give me example to understand what is a module ? (and please don't just explain it with words give an example of a spm) Thanks in advance.

1 Like

You may want to read the section on Access Control from The Swift Book, which includes this definition:

A module is a single unit of code distribution — a framework or application that’s built and shipped as a single unit and that can be imported by another module with Swift’s import keyword.

It does seem like a pretty nebulous term, but likely intentionally so. :man_shrugging:

unfortunately, this isn't helping at all. what I am looking for is a real example. because all what I found on the internet is just plain of some text. But I appreciate your reply.

Module is effectively every product (external exposure) or target (internal exposure) you have defined in Package.swift.

so for example here :

import PackageDescription

let package = Package(
    name: "AudioExtraction",
    platforms: [
      .iOS(.v17),
      .macOS(.v14)
    ],
    products: [
        .library(
            name: "AudioExtraction",
            targets: ["AudioExtraction"]),
    ],
    targets: [
        .target(
            name: "AudioExtraction",
            resources: [
              .process("Resources")
            ]
        ),
        .testTarget(
            name: "AudioExtractionTests",
            dependencies: ["AudioExtraction"]),
    ]
)

AudioExtraction is a module and if I wanted to add another one I will add it in the targets ? @vns

There are three same names, so:

  • package name, at the top, relevant to the package only
  • library product (or executable) - module you'll expose to others (user of this package), so that they can import it when this package added as dependency
  • target - module you have internally and can use as dependency for other targets in this package or create product out of it

But essentially yes,

To add a module you'll need to have a target first anyway.

1 Like

So to have a new target I will add a new folder in the sources folder like this:
Screenshot 2024-07-11 at 12.22.12 PM

then I will add this to the targets like that:

let package = Package(
    name: "AudioExtraction",
    platforms: [
      .iOS(.v17),
      .macOS(.v14)
    ],
    products: [
        .library(
            name: "AudioExtraction",
            targets: ["AudioExtraction"]),
    ],
    targets: [
        .target(
            name: "AudioExtraction",
            resources: [
              .process("Resources")
            ]
        ),
        .target(name: "newTarget"),
        
        .testTarget(
            name: "AudioExtractionTests",
            dependencies: ["AudioExtraction"]),
    ]
)

is that correct ? @vns

Documentation on the topic is quite good:
Swift.org - Package Manager. I think it will give more value than this incomplete answers (btw I am wrong that products are modules, doc defines module only as a target).

2 Likes

i think the terms SwiftPM uses are a bit confusing, and i believe there is already an effort underway to switch to a simpler naming system.

module and target are the same concept. a project always has exactly one module per target, and vice versa. i believe today the term module is preferred over target, because we sometimes use target as a synonym for platform, a completely unrelated concept.

a product is technically a collection of modules, which always includes all of the transitive dependencies of those modules. although it is not a technical requirement, best practice is to define one product per module (with the same name as that module!), which adds another near-synonym to the mix. however, we usually think of a product as containing not only its eponymous module, but also all of that module’s dependencies.

a package is also a collection of modules, which often (but not always) contains an equal number of products, since we usually have one product per module. however, it’s not uncommon to have fewer products than there are modules, as some of the modules may be internal (tests, benchmarks, etc.)

6 Likes

There's also a deployment target (Darwin-specific terminology as far as I'm aware) and a build system target (not exposed anywhere publicly, but is somewhat pervasive in the SwiftPM codebase).

But overall that's correct, target is used in the public API of Package.swift for historical reasons, is a confusing term with at least 4 different meanings, and in the SwiftPM codebase itself internally we're trying to refer to it as a "module" to reduce the ambiguity.

I wouldn't call that a best practice. Only declare products if you need other packages to have access to your modules. A product is basically a public collection of modules. In the same way public access control on a declaration is not the default, one should not declare products in their package unless absolutely necessary, otherwise other packages can start depending on it, with all SemVer and compatibility implications of that.

1 Like

you’re right, i was drawing contrast to the possibility of having multiple modules combined into a single product with no finer-grained grouping to allow for depending on its constituents by themselves.

If this is true, is there also a proposal to rename the targets property in the Package definition to modules? Because otherwise, I believe this practice is probably making things more confusing for everyone.

2 Likes

Fast search on GitHub show that package access level is used in several projects. Search query: "package func" language:Swift

Results:
package func prepare(query: String) throws -> PreparedStatement
(swift-package-manager/Sources/Basics/SQLite.swift at dca0cc27b9d5f08a9c9a38101e322d0f3ab1ba03 · swiftlang/swift-package-manager · GitHub)

package func getRules(excluding excludingOptions: ExcludingOptions) -> RuleList
(SwiftLint/Source/SwiftLintFramework/RulesFilter.swift at e9ce8ce8bb44e1525c20c141e8e54ae1d427cc46 · realm/SwiftLint · GitHub)

Inspecting these projects should help to understand how to make a package with several modules.

package func withLock(_ closure: (inout T) -> U) -> U
(Pulse/Sources/Pulse/Helpers/Mutex.swift at c102aaa266ac69a26d08ee6861da710283988e3b · kean/Pulse · GitHub)