mackoj/packagegeneratorplugin
⚠️ This is in beta
First Launch
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.
If the configuration file is missing, the plugin will create a default template and stop so you can fill in the required values before rerunning it.
Each time you need to add a module remember to add it to the configuration file.
How does it work?
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.
Installation
Add to your dependencies .package(url: "https://github.com/mackoj/PackageGeneratorPlugin.git", from: "0.5.0"),
Basic usage
The plugin will display messages and errors in Xcode Report navigator.
| step | description | img | | --- | --- | --- | | 0 | To run it right click on the package you want to run it on. | [Capture d’écran 2022-11-07 à 11 04 05] | | 1 | It will propose you to run it you can provide an optional argument(--confFile packageGenerator.yaml) in the argument pane, which will allow you to change the name of the configuration file. Once change the new configuration file name will be stored | [Capture d’écran 2022-11-07 à 11 05 28] | | 2 | At first launch, it will ask for permission to write files into the project directory for it to work you have to select "Allow Command to Change Files". | [Capture d’écran 2022-12-14 à 15 38 34] |
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.
Configuration
To use it you have to set a configuration file at the root of your project named `packageGenerator.yaml`, `packageGenerator.yml`, or `packageGenerator.json`.
The plugin will auto-detect an existing file in that order, then read and write it using the format implied by its extension.
This file contains these keys:
- `packageDirectories`: An array where each entry can either be a legacy string/object describing a single target or a new object mirroring the CLI `result.json` (containing `path` plus a `targets` array). Each target entry may specify `name`, `type`, optionally `path`, and optionally `exclude`, which the plugin will honor when generating `Package.swift`.
- `packageDirectoryTargets`: An array of objects that describe directories and the targets they contain. Each entry has a `path` and a `targets` array of objects (`name`, `type` equals `regular` or `test`, plus optional overrides such as `path` or `regularTargetName`). Targets default to `<path>/Sources/<name>` (or `<path>/Tests/<name>` for tests) and tests are paired to their regular target using the `Tests` suffix or the explicit `regularTargetName`. If no match is found, the plugin falls back to attaching the test target to the first available regular target in the same group so it is still generated.
- `headerFileURL`: A string that represents the path of the file that will be copied at the top of the `Package.swift`
- `spaces`: An int that represents the number of spaces that the `Package.swift` generator should use when adding content
- `verbose`: A bool that represents if it should print more information in the console
- `pragmaMark`: A bool that represents if we should add `// MARK: -` in the generated file
- `dryRun`: A bool that represents if the generator should replace the `Package.swift` file or create a `Package_generated.swift`
- `mappers.targets`: An dictionary that handles target renaming the key represents a target `path` with the `/` and the value represents the name to apply. For example in the `packageDirectories` I have `Sources/App/Helpers/Foundation` but in my code, I import `FoundationHelpers`.
- `mappers.imports`: An dictionary that represents how to map import that requires a `.product` in SPM for example `ComposableArchitecture` require to be called `.product(name: "ComposableArchitecture", package: "swift-composable-architecture")` in a `Package.swift`.
- `exclusions`: An object that represents all imports that should not be added as dependencies to a target or targets in the generated `Package.swift`
- `exclusions.apple`: An array of strings for extra Apple SDKs you want to exclude. A comprehensive list of Apple frameworks is already filtered by default (see `Plugins/PackageGenerator/AppleSDKs.swift`), so this only needs to contain anything beyond those defaults.
- `exclusions.imports`: An array of string that represents all other SDK that should not be added as dependencies to a target
- `exclusions.targets`: An array of string that represent all targets that should not be added in the generated `Package.swift`
- `targetsParameters`: An dictionary that represent what custom parameter to add to a target
- `generateExportedFiles`: A bool that represents if the generator should create `exported.swift` files in each package with `@_exported import` statements for local dependencies
- `exportedFilesRelativePath`: An optional string that specifies a relative path within each package where the `exported.swift` files should be placed. If not specified, files are placed in the package root directory.
Apple frameworks defined in `Plugins/PackageGenerator/AppleSDKs.swift` are excluded automatically, so you only need to add entries to `exclusions.apple` if you want to append additional Apple SDKs that are not already covered.
```json
{
"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,
"generateExportedFiles": false,
"exportedFilesRelativePath": "Generated",
"mappers": {
"targets": {
"Sources/App/Helpers/Foundation/": "FoundationHelpers",
},
"imports": {
"ComposableArchitecture": ".product(name: \"ComposableArchitecture\", package: \"swift-composable-architecture\")"
}
},
"exclusions": {
"apple": [
"CustomAppleFramework"
],
"imports": [
"PurchasesCoreSwift"
],
"targets": [
"ParserCLI"
]
}
}
```
The same configuration can also be written in YAML:
```yaml
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
generateExportedFiles: false
exportedFilesRelativePath: Generated
mappers:
targets:
Sources/App/Helpers/Foundation/: FoundationHelpers
imports:
ComposableArchitecture: '.product(name: "ComposableArchitecture", package: "swift-composable-architecture")'
exclusions:
apple:
- CustomAppleFramework
imports:
- PurchasesCoreSwift
targets:
- ParserCLI
```
If you need to register multiple targets under the same directory, use `packageDirectoryTargets` instead of `packageDirectories`. The plugin will derive each target’s path as described above and attach a test target when one is configured.
```json
{
"packageDirectoryTargets": [
{
"path": "Packages/CoreMobileServices",
"targets": [
{ "name": "Keys", "type": "regular" },
{ "name": "CoreMobileServices", "type": "regular" },
{ "name": "CoreMobileServicesTests", "type": "test" }
]
}
],
"headerFileURL": "header.swift"
}
```
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.
### Header File
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**(https://github.com/mackoj/PackageGeneratorPlugin/issues/8).
```swift
// 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"
),
]
)
```
### Exported Files
When `generateExportedFiles` is set to `true` in the configuration, the plugin will generate an `exported.swift` file in each package directory that contains `@_exported import` statements for all local dependencies of that package.
This improves developer experience by automatically re-exporting local dependencies, so users don't need to manually import all the dependencies they need in their code.
For example, if package `MyPackage1` depends on `Chip` and `Logger`, the generated `exported.swift` will contain:
```swift
// This file is auto-generated by PackageGeneratorPlugin
// It exports all local dependencies for this package
@_exported import Chip
@_exported import Logger
```
In dry run mode, the files will be named `exported_generated.swift` to allow you to review the output before enabling the feature.
#### Exported Files Relative Path
You can customize where the exported files are placed within each package by setting the `exportedFilesRelativePath` parameter. This allows for better organization of your generated files.
- If not specified (or `null`), exported files are placed directly in each package's root directory
- If specified (e.g., `"Generated"`), exported files are placed in the specified subdirectory within each package
For example, with `"exportedFilesRelativePath": "Generated"`, the exported.swift file for a package at `Sources/MyPackage/` would be created at `Sources/MyPackage/Generated/exported.swift`.CI
You can use it in CI to automatically generate your Package.swift.
swift package plugin --allow-writing-to-package-directory package-generator
FAQ
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.swiftfile.
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
.packageGeneratorfor 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.
Package Metadata
Repository: mackoj/packagegeneratorplugin
Default branch: main
README: README.md