Swift Package Manager describes how source files become modules, how modules depend on one another, and which products consumers can use. A package is more than a folder of Swift files: its manifest establishes build relationships and the tools version required to interpret its configuration.
Review the functions lesson and the original testing chapter. This guide builds a small reusable library plus a command-line executable. It does not require SwiftUI or an Apple-only framework, so the package structure is useful across supported Swift platforms with an appropriate installed toolchain.
Distinguish package, target and product
Package.swift is a Swift manifest using the PackageDescription API. A target groups sources into a module or another supported build unit. A product publishes a library or executable assembled from target content. A product's name and a target's module name can differ, so import the module rather than guess from the package's marketing name.
Targets declare which other targets or dependency products they use. Merely adding a package dependency does not make every module available to every target. The graph must connect the target that imports a module to the corresponding target or product dependency.
The tools-version comment selects the minimum tools behavior used to interpret the manifest. It is not simply an application version or an operating-system deployment target. Package platform declarations, language modes and dependency compatibility are separate concerns. Pin an understandable example before adding conditional platform or build settings.
Dependencies are a reviewed graph
An external package dependency declares a source and version requirement, while a target's product dependency names the product it actually uses. Semantic-version ranges can allow compatible releases under the producer's contract, and a resolved file records the selected versions for the applicable workflow.
Review dependency changes as executable build inputs. A new repository or package can bring transitive code, plugins or binary artifacts into the graph. Do not add dependencies merely to solve a tiny operation the standard library already expresses clearly. Reuse is useful when the dependency's maintenance and compatibility obligations are understood.
Commit Package.resolved where the project's role and team workflow require reproducible selected versions, especially applications. Library packages also express requirements for their consumers, so distinguish a library's allowed range from one local resolution. Follow the current SwiftPM guidance rather than assume one lockfile practice serves every package role.
Source layout and access failures
Library sources normally live under Sources followed by the target name, and tests under Tests followed by the test target name. Files in a directory do not become a separate module unless the manifest establishes that target. Avoid placing two entry-point styles into one executable accidentally.
An unresolved import can mean a missing target dependency, a wrong module name or unavailable platform support. An inaccessible function can mean the library declaration is internal rather than public. A dependency-resolution error occurs before application code compilation and needs a different fix from a type error in Stock.swift.
Use command-line builds as evidence alongside IDE integration. A successful editor autocomplete session does not establish that a clean package builds elsewhere. Keep credentials out of Package.swift and dependency URLs; configure authenticated access through supported environment or tooling mechanisms without committing secrets into source.
Official references
The SwiftPM command-line introduction walks through package workflows, and the package manager documentation covers manifest and build concepts. Review the graph from each importing target: which module is needed, where it comes from, which version is selected and which platform supports it. This turns a dependency error into a concrete missing relationship.
Swift Package Manager Project Structure Examples
Each example shows the code, the output it produces and what each part does.
A complete library and executable package
Use a Swift 5.9 or later toolchain for this manifest. Place each file at the shown relative path in one package directory. The library exposes its function publicly because the executable is another module.
From the package directory, run swift build and then swift run stock-demo. Build progress varies by toolchain and environment; the executable's output is:
Use the explicit executable product name when several executable products exist. The Sources layout follows SwiftPM conventions, so no custom path configuration is needed. Keep generated build artifacts outside the source contract.
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "StockDemo",
products: [
.library(name: "StockCore", targets: ["StockCore"]),
.executable(name: "stock-demo", targets: ["StockCLI"])
],
targets: [
.target(name: "StockCore"),
.executableTarget(name: "StockCLI", dependencies: ["StockCore"]),
.testTarget(name: "StockCoreTests", dependencies: ["StockCore"])
]
)public func stockTotal(_ quantities: [Int]) -> Int {
quantities.reduce(0, +)
}import StockCore
print("Stock total: \(stockTotal([2, 4, 3]))")Stock total: 9Swift Package Manager Project Structure in Real Projects
Worked exercise: Test the library module
Add a focused test covering a normal total and empty input. XCTest is used here because the manifest's baseline is Swift 5.9; the original testing lesson also discusses newer Swift Testing. Place this complete test file in the declared test target.
import XCTest
@testable import StockCore
final class StockTests: XCTestCase {
func testTotalAndEmptyInput() {
XCTAssertEqual(stockTotal([2, 4, 3]), 9)
XCTAssertEqual(stockTotal([]), 0)
}
}Run swift test. The exact harness output varies, but the expected result is:
StockTests.testTotalAndEmptyInput passes
0 test failuresThis fence states the expected test outcome rather than promising verbatim platform-specific harness text. The tested library operation is pure and needs no network access. If quantities can be large or invalid, extend the function's domain contract and test overflow or rejection deliberately instead of treating this simple sum as a production inventory service.
Swift Package Manager Project Structure FAQ
Is a product the same thing as a target?
No. A target is a build unit that commonly becomes a module, while a product is something the package exposes, such as a library or executable built from target content. The names can coincide but need not. Import the target's module name in source and refer to the published product when declaring an external product dependency. Keeping these roles distinct helps diagnose missing imports and incorrect target relationships.
Does the tools-version comment choose an iOS version?
No. It states the tools version needed to interpret the manifest and influences supported package features. Apple deployment targets belong in the package's platform configuration when relevant. A modern compiler can still build code intended for an older supported OS if its APIs and configuration allow it. Keep toolchain availability, language mode and operating-system API availability separate when reviewing compatibility rather than treating one version number as all three.
Why is the library function public in this example?
The executable target imports StockCore as a separate module. Internal declarations are not ordinarily visible to that importing module, so the supported library function must be public. The test target can use testable import for internal testing when compiled with testing enabled, but that does not publish internal APIs to real consumers. A reusable package should expose a deliberate small interface and keep implementation helpers internal or private.
Should I commit generated build output?
Normally the source, manifest and relevant resolution information form the reviewable package contract, while generated build output is recreated by the tools. Committing machine-specific artifacts can obscure changes and break portability. Follow the project's packaging requirements if a deliberate binary product is distributed, but do not confuse that reviewed artifact with ordinary local cache directories. A clean build should be possible from the documented source and selected dependencies without copying one developer's build folder.
Swift Package Manager Project Structure Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
An external package is declared but a target still cannot import its module. What should be checked?
Swift Package Manager Project Structure Quiz
2 questions to check what you learned.
What normally groups source files into an importable module?
- A declared target.
- The executable's printed output.
- Any arbitrary folder name with no manifest relationship.
Why can StockCLI call stockTotal?
- The product's lowercase name disables access control.
- It depends on StockCore and the function is public across modules.
- Every package file automatically shares private access.