Swift Package Manager Series 03|Key concepts in Package.swift
What is really difficult is understanding what products, targets, and dependencies mean in the project boundary.
When you open Package.swift for the first time, your first impression will be: the file is not long and the syntax is not complicated.
But it’s easy to get confused once you actually start the project, because the difficulty is not at all in “does the syntax look like Swift”, but in the fact that several core concepts in it actually correspond to the project structure, not ordinary configuration items.
If you just think of it as a “list to fill in a few fields”, you will often feel that you know every word, but it will easily become confusing when you change it. What really needs to be understood is what questions these concepts answer.
The most important thing is to capture these core roles first:
- package
- products
- dependencies
- targets
1. Package.swift describes module relationships
This is the first step to understanding it.
A common situation is that you subconsciously think of it as:
-Project configuration file
- Depends on installation files
- or “Podspec for Swift”
But from an engineering perspective, Package.swift is more like a module relationship specification.
It’s telling the toolchain:
- What does this package provide to the outside world?
- Which external packages does it depend on?
- What targets are composed of internally?
- How these targets are related to each other
So it focuses on “how this code should be organized into modules and used by others.”
2. products answers: What capabilities does this package want to give to the outside world?
This is the most easily overlooked point when initially dealing with this type of problem.
target is the internal building unit, and product is the externally consumable result.
In other words, the question answered by product is:
What is this package ultimately intended to expose to its users?
There is a very important design signal behind this.
If you have no idea about product, it is easy to confuse the internal structure of the package with its external usage.
Of course, there can be multiple targets inside a package, but the outside does not necessarily need to know all these internal splits.
So product is essentially packaging the external interface.
If you understand it from this perspective, you will think more naturally:
- Which internal targets are implementation details
- Which abilities really need to be exposed?
- What level should external dependents see?
3. dependencies answers: What capabilities does this package borrow from the outside world?
When I saw this situation dependencies, I just thought of “installing a third-party library”.
But from an engineering perspective, its more important significance is:
Outside the boundaries of this module, what external world does it depend on.
This will directly affect:
- Is the module light enough?
- Is it expensive to compile?
- Is it easy to reuse in the future?
- Will it also bind the host project into some external dependencies?
If a module has too many external dependencies, it will often cause two problems:
- It’s no longer lightweight
- Its boundaries are also starting to become unclear
So when looking at dependencies, don’t just think about “can it be installed”, but also think about “why does this module need to rely on these things”.
4. targets answers: How should the internal code be organized and compiled?
This is the part of Package.swift that is most easily regarded as “folder mapping”, but it is actually much more than this.
target really corresponds to:
- A group of codes that are compiled together
- An explicit dependency unit
- an internal module boundary
When you define target, you are deciding:
- Which codes should evolve together
- Which implementations should be placed in the same compilation unit
- Which tests should correspond to this boundary
So target is one of the smallest carrying units of internal modularity.
If you break the target into too many pieces, the project will become heavier. If the target is made too large, the boundaries will be blurred. This also shows that Package.swift seems to be just a configuration, but actually carries a lot of architectural judgments.
5. Confuse product with target
Because in simple packages, they often correspond one to one.
For example:
- a target
- a library product
At this time, it is easy to mistakenly think that the two concepts are similar. But once the project gets a little more complicated, you’ll find:
- A package can have multiple targets
- Multiple targets can serve one product
- Some targets are only internal implementations and should not be exposed directly
So my experience is: **Understand target as the internal structure and product as the external outlet. **
This way the thinking will become much clearer at once.
6. Testing target is also very important
When you first see Package.swift, you will regard the test target as an accessory configuration.
But from the perspective of modular thinking, it is actually very important.
Because the value of a module is not worthy of independent existence, it is also reflected to a large extent in:
- Can it be independently tested
- Whether its dependency boundaries are clear enough
- Whether it can verify core behavior without relying on the host app
If a module has to rely on half of the host project for every test, then although it is a Package in name, the boundaries may not be independent in fact.
7. A more practical reading order
If I open Package.swift for an unfamiliar project for the first time, I usually look at it in this order:
productsLet’s first look at what it exposes to the outside world.dependenciesLet’s look at what it borrowed from the outside worldtargetsFinally, let’s see how the interior is dismantled.
The benefits of this sequence are: Look at the boundaries first, then look inside, rather than getting bogged down in implementation details.
8. Conclusion: The difficulty of Package.swift is not in the syntax, but in that it actually describes the project boundaries.
To put it in shorter form, I would say:
Package.swiftThe most important thing is whether you can understand the boundary relationship behindproduct,dependency, andtarget.
Once understood from this perspective, this file is no longer just a configuration, but a condensed expression of the module design.
读完之后,下一步看什么
如果还想继续了解,可以从下面几个方向接着读。