Package Generator is a Swift Package Manager Plugin for simply updating your Package.swift
file consistently and understandably. This is a great tool for projects that are heavily modularized or use TCA and thus rely on a clean and updated Package.swift
.
Package Generator adds imports that it read from the source code files to their target in Package.swift
. This will help reduce compilation issues with SwiftUI Preview too.
After installing it you will be able to run it but for it to work properly it needs to be configured. By default, it will run with dry-run
set to true and this will create a file Package_generated.swift
to allow you to preview what will happen. After having properly configured it and testing that the Package_generated.swift
generate the correct content you will need to set dry-run
to false in the configuration to write in the real Package.swift
file.
Each time you need to add a module remember to add it to the configuration file.
Package Generator goes to all folders set in the configuration then read all swift files to look at all the imports to create a target to add to the Package.swift.
The code analyzing part is made using swift-syntax since I didn't find a way to link it to the plugin I have to package it in a CLI that is used to do the parsing part.
Add to your dependencies .package(url: "https://github.com/mackoj/PackageGeneratorPlugin.git", from: "0.5.0"),
The plugin will display messages and errors in Xcode Report navigator.
By default to prevent any surprise it will do a dry-run(not modifying your Package.swift
but creating a Package_generated.swift
) for you to allow you time to review it before using it.
To use it you have to set a configuration file at the root of your project named packageGenerator.json
.
This file contains these keys:
packageDirectories
: An array of string that represents where the modules areheaderFileURL
: A string that represents the path of the file that will be copied at the top of thePackage.swift
spaces
: An int that represents the number of spaces that thePackage.swift
generator should use when adding contentverbose
: A bool that represents if it should print more information in the consolepragmaMark
: A bool that represents if we should add// MARK: -
in the generated filedryRun
: A bool that represents if the generator should replace thePackage.swift
file or create aPackage_generated.swift
mappers.targets
: An dictionary that handles target renaming the key represents a targetpath
with the/
and the value represents the name to apply. For example in thepackageDirectories
I haveSources/App/Helpers/Foundation
but in my code, I importFoundationHelpers
.mappers.imports
: An dictionary that represents how to map import that requires a.product
in SPM for exampleComposableArchitecture
require to be called.product(name: "ComposableArchitecture", package: "swift-composable-architecture")
in aPackage.swift
.exclusions
: An object that represents all imports that should not be added as dependencies to a target or targets in the generatedPackage.swift
exclusions.apple
: An array of string that represents all Apple SDK that should not be add as dependencies to a targetexclusions.imports
: An array of string that represents all other SDK that should not be added as dependencies to a targetexclusions.targets
: An array of string that represent all targets that should not be added in the generatedPackage.swift
targetsParameters
: An dictionary that represent what custom parameter to add to a target
{
"packageDirectories": [
"Sources/App/Clients/Analytics",
"Sources/App/Clients/AnalyticsLive",
"Sources/App/Daemons/Notification",
"Sources/App/Helpers/Foundation"
],
"headerFileURL": "header.swift",
"targetsParameters": {
"Analytics": ["exclude: [\"__Snapshots__\"]", "resources: [.copy(\"Fonts/\")]"],
"target2": ["resources: [.copy(\"Dictionaries/\")]"]
},
"verbose": false,
"pragmaMark": false,
"spaces": 2,
"dryRun": true,
"mappers": {
"targets": {
"Sources/App/Helpers/Foundation/": "FoundationHelpers",
},
"imports": {
"ComposableArchitecture": ".product(name: \"ComposableArchitecture\", package: \"swift-composable-architecture\")"
}
},
"exclusions": {
"apple": [
"ARKit",
"AVFoundation"
],
"imports": [
"PurchasesCoreSwift"
],
"targets": [
"ParserCLI"
]
}
}
If a new configuration filename is used as explained in #basic-usage step 1. It will be saved so that you will not be required to input the configuration fileName at each launch.
The content of headerFileURL
from the configuration will be added to the top of the generated Package.swift
.
I advise adding all required dependencies
and Test Targets, System Librarys, Executable Targets and Binary Targets(#8).
// swift-tools-version:5.7
// The swift-tools-version declares the minimum version of Swift required to build this package.
import Foundation
import PackageDescription
var package = Package(
name: "project",
defaultLocalization: "en",
platforms: [
.macOS(.v12),
.iOS("15.0")
],
products: [
.executable(name: "server", targets: ["server"]),
.executable(name: "parse", targets: ["ParserRunner"]),
],
dependencies: [
.package(url: "https://github.com/mackoj/PackageGeneratorPlugin.git", from: "0.3.0"),
.package(url: "https://github.com/mackoj/SchemeGeneratorPlugin.git", from: "0.5.5"),
.package(url: "https://github.com/pointfreeco/swift-composable-architecture.git", from: "0.45.0"),
],
targets: [
// MARK: -
// MARK: Test Targets
.testTarget(
name: "MyProjectTests",
dependencies: [
"MyProject",
]
),
// MARK: -
// MARK: Executables
.executableTarget(
name: "server",
path: "Sources/Backend/Sources/Run"
),
.executableTarget(
name: "ParserRunner",
path: "Sources/App/Parsers/Runner"
),
]
)
You can use it in CI to automatically generate your Package.swift
.
swift package plugin --allow-writing-to-package-directory package-generator
Why is the plug-in is not visible in Xcode?
Plug-in can work if you do a right click on your project package and only if the Resolves Packages
is passing without issue.
Why does the plugin have an executable dependency?
Because we cannot import other packages in an SPM Plugin and we need swift-syntax to parse code and extract imports.
It always creates an invalid
Package.swift
file.
Look at the Report Navigator
in Xcode it might be due to imports that don't exist or that require the use of mappers-imports.
Why doesn't it use a hidden file like
.packageGenerator
for configuring the tool?
Because it would not be visible in Xcode and this file might need to be edited often. But you can change this if you want by giving the --confFile
argument when using the tool.