'How to integrate SwiftLint with an iOS app using Swift Package Manager?
I'm creating a new iOS app in Xcode 11 (beta 5) and I'd like to try using Swift Package Manager instead of CocoaPods for managing dependencies.
A common pattern when using SwiftLint and CocoaPods is to add SwiftLint as a dependency and then add a build phase to execute ${PODS_ROOT}/SwiftLint/swiftlint; this way all developers end up using the same version of SwiftLint.
If I try to add SwiftLint as a SwiftPM dependency in Xcode, the executable target that I need is disabled:
I was able to fake it by creating a dummy Package.swift with no products or targets and running swift run swiftlint in my build phase, but it feels hacky and weird:
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "dummy-package",
products: [],
dependencies: [
.package(url: "https://github.com/realm/SwiftLint.git", from: "0.34.0")
],
targets: []
)
Is there a way do this without creating a dummy package? Or is Swift Package Manager just not the right tool for this particular use case?
Solution 1:[1]
I use xcodegen to genreate a Xcode project that has the ability to run scripts. This lets me see swiftlint warnings in Xcode while developing packages.
This tool creates a Xcode project from a project.yml definition. In that definition, you can add a script that runs swiftlint as a post compile task. Example.
Advantages of this method:
- swiftlint warnings in Xcode.
- Xcode settings beyond what SPM offers.
Disadvantages:
- You rely on a third-party tool that could break or go away. However, you can drop this dependency at any time and go back to edit the Package.swift in Xcode.
- You need to learn to write project.yml files.
- If you use SPM bundles you need to generate the bundle accessor yourself.
A word about generating a bundle accessor. This is needed when working from a Xcode project because only SPM generates the file resource_bundle_accessor.swift to the project. If you already compiled after opening the Package.swift with Xcode, the file should be here:
find ~/Library/Developer/Xcode/DerivedData* -iname resource_bundle_accessor.swift
You can add it to the project, but if you are creating a framework, the bundle accessor can be as simple as:
import class Foundation.Bundle
// This file is only used when using a xcodegen-generated project.
// Otherwise this file should not be in the path.
private class BundleFinder {}
extension Foundation.Bundle {
static var module = Bundle(for: BundleFinder.self)
}
Solution 2:[2]
Not perfect solution, but it works. I've found it here
- Modify target section of
Package.swift
targets: [
// 1. Specify where to download the compiled swiftlint tool from.
.binaryTarget(
name: "SwiftLintBinary",
url: "https://github.com/realm/SwiftLint/releases/download/0.47.1/SwiftLintBinary-macos.artifactbundle.zip",
checksum: "cdc36c26225fba80efc3ac2e67c2e3c3f54937145869ea5dbcaa234e57fc3724"
),
// 2. Define the SPM plugin.
.plugin(
name: "SwiftLintPlugin",
capability: .buildTool(),
dependencies: ["SwiftLintBinary"]
),
// 3. Use the plugin in your project.
.target(
name: "##NameOfYourMainTarget",
dependencies: ["SwiftLintPlugin"] // dependency on plugin
)
]
- Create
Plugins/SwiftLintPlugin/SwiftLintPlugin.swift
import PackagePlugin
@main
struct SwiftLintPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
return [
.buildCommand(
displayName: "Running SwiftLint for \(target.name)",
executable: try context.tool(named: "swiftlint").path,
arguments: [
"lint",
"--in-process-sourcekit",
"--path",
target.directory.string,
"--config",
"\(context.package.directory.string)/.swiftlint.yml" // IMPORTANT! Path to your swiftlint config
],
environment: [:]
)
]
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Constantine Nikolsky |

