Awesome
PackageConfig
A Swift Package that allows you to define configuration settings inside a Package.swift
- this is so that tools can all keep their configs consolidated inside a single place.
Tool builders use this dependency to grab their config settings.
Package Configuration
The fastest way to insert a configuration in your Package.swift
is to add PackageConfig
to your dependencies
.package(url: "https://github.com/shibapm/PackageConfig.git", from: "0.13.0")
And add the configuration right at the bottom of your Package.swift
e.g.
#if canImport(PackageConfig)
import PackageConfig
let config = PackageConfiguration([
"komondor": [
"pre-push": "swift test",
"pre-commit": [
"swift test",
"swift run swiftformat .",
"swift run swiftlint autocorrect --path Sources/",
"git add .",
],
],
"rocket": [
"after": [
"push",
],
],
]).write()
#endif
Custom Configuration Types
PackageConfig
offers also the possibility to create your own configuration type
User writes:
Run this line to have empty source for PackageConfigs
target generated for you.
swift run package-config
First time it should return an error error: no target named 'PackageConfigs'
.
Now you can list all the required package configs anywhere in the list of targets in Package.swift
like this.
// PackageConfig parses PackageConfigs target in Package.swift to extract list of dylibs to link when compiling Package.swift with configurations
.target(name: "PackageConfigs", dependencies: [
"ExampleConfig" // some executable configuration definition dylib
])
At the very bottom of the Package.swift
#if canImport(ExampleConfig) // example config dynamic library
import ExampleConfig
// invoking write is mandatory, otherwise the config won't be written // thanks captain obvious
let exampleConfig = ExampleConfig(value: "example value").write()
#endif
If more than one dependency uses PackageConfig
be sure to wrap each in
#if canImport(SomeLibraryConfig)
import SomeLibraryConfig
let someLibraryConfig = SomeLibraryConfig().write()
#endif
Be sure to invoke write
method of the Config
otherwise this won't work.
And then to use executable user would need to run this in the same directory as his/her project Package.swift
swift run package-config # compiles PackageConfigs target, expecting to find a dylib in `.build` directory for each of the listed libraries configs
swift run example # runs your library executable
Tool-dev writes:
For the sake of example lets assume your library is called Example then Package.swift
would look like this:
let package = Package(
name: "Example",
products: [
// notice that product with your library config should be dynamic library in order to produce dylib and allow PackageConfig to link it when building Package.swift
.library(name: "ExampleConfig", type: .dynamic, targets: ["ExampleConfig"]),
//
.executable(name: "example", targets: ["Example"]),
],
dependencies: [
.package(url: "https://github.com/shibapm/PackageConfig.git", from: "0.0.2"),
],
targets: [
.target(name: "ExampleConfig", dependencies: ["PackageConfig"]),
.target(name: "Example", dependencies: ["ExampleConfig"]),
]
)
In your ExampleConfig
target define ExampleConfig
like this.
import PackageConfig
// it must be public for you to use in your executable target
// also you must conform to `Codable` and `PackageConfig`
public struct ExampleConfig: Codable, PackageConfig {
// here can be whatever you want as long as your config can stay `Codable`
let value: String
// here you must define your config fileName which will be used to write and read it to/from temporary directory
public static var fileName: String { return "example-config.json" }
// public init is also a requirement
public init(value: String) {
self.value = value
}
}
Then for example in your main.swift
in executable Example
target you can load your config like this:
import ExampleConfig
do {
let config = try ExampleConfig.load()
print(config)
} catch {
print(error)
}
Notes for library developers
Since YourConfig
target is a dynamic library you must ensure that you have built it everytime when using either read
or write
methods of PackageConfig
. When building from terminal this can be done by just running swift build
.
Changelog
-
0.0.1
Exposes a config for
Package.swift
files#if canImport(PackageConfig) import PackageConfig let config = PackageConfig([ "danger" : ["disable"], "linter": ["rules": ["allowSomething"]] ]).write() #endif
This might be everything, so if in a month or two nothing really changes I'll v1 after this release.
How it all works
When you invoke YourPackage.load()
it will compile the Package.swift
in the current directory using swiftc
.
While compiling it will try to link list of dynamic libraries listed in PackageConfigs
target.
When it is compiled, PackageConfig will run and when YourPackage.write()
is called your package configuration json will be written to temporary directory.
After that it will try to read the json and decode it as if it was YourPackage
type, providing it back to where you have invoked load
method.
Debugging
How to see the JSON from a Package.swift file
Use SPM with verbose mode:
~/d/p/o/i/s/PackageConfig $ swift build --verbose
And grab the bit out after the first sandbox. I then changed the final arg to -fileno 1
and it printed the JSON.
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc --driver-mode=swift -L /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4_2 -lPackageDescription -suppress-warnings -swift-version 4.2 -I /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk /Users/ortatherox/dev/projects/orta/ios/spm/PackageConfig/Package.swift -fileno 1
{"errors": [], "package": {"cLanguageStandard": null, "cxxLanguageStandard": null, "dependencies": [], "name": "PackageConfig", "products": [{"name": "PackageConfig", "product_type": "library", "targets": ["PackageConfig"], "type": null}], "targets": [{"dependencies": [], "exclude": [], "name": "PackageConfig", "path": null, "publicHeadersPath": null, "sources": null, "type": "regular"}, {"dependencies": [{"name": "PackageConfig", "type": "byname"}], "exclude": [], "name": "PackageConfigTests", "path": null, "publicHeadersPath": null, "sources": null, "type": "test"}]}}
How I verify this works
I run this command:
swift build; env DEBUG="*" swift run package-config-example
if you don't use fish:
swift build; DEBUG="*" swift run package-config-example