Plugins, Run Scripts, and Integrations
Setup instructions for each SwiftLint integration method: build tool plugin, command plugin, Xcode run scripts, CI, and secondary integrations.
Contents
- Build Tool Plugin (Recommended)
- Command Plugin
- Xcode Run Script Build Phase
- CI Recipes
- Working With Multiple Swift Versions
- VS Code
- Fastlane
- Docker
- Pre-commit Hook
Build Tool Plugin (Recommended)
The SwiftLintBuildToolPlugin from SimplyDanny/SwiftLintPlugins runs SwiftLint as part of the build. No Homebrew or PATH setup needed.
SwiftPM setup
// Package.swift
let package = Package(
name: "MyApp",
dependencies: [
.package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", from: "<reviewed-version>")
],
targets: [
.target(
name: "MyApp",
plugins: [.plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLintPlugins")]
),
.testTarget(
name: "MyAppTests",
dependencies: ["MyApp"],
plugins: [.plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLintPlugins")]
)
]
)Xcode project setup (no Package.swift)
- File > Add Package Dependencies → add
https://github.com/SimplyDanny/SwiftLintPlugins - For each target you want to lint, go to Build Phases and add
SwiftLintBuildToolPluginunder Run Build Tool Plug-ins - When prompted, trust the plugin
Plugin trust
On first build, Xcode shows a trust dialog. Select Trust & Enable All for the SwiftLintPlugins package. In CI with xcodebuild, pass:
xcodebuild -skipPackagePluginValidation -skipMacroValidation ...These unattended flags bypass Xcode's validation dialogs and implicitly trust package plugins and macros. Use them only for reviewed dependencies in controlled CI.
Limitations
- The build tool plugin cannot run
--fix(it has read-only access to sources). - It cannot pass
--baselineor other CLI flags — build tool plugins do not accept arguments. Use config keys likebaseline:/write_baseline:where available, or switch to the command plugin / direct CLI for advanced flag-based workflows. - It may fail when Swift files or the config live outside the package/project directory because it cannot pass
--config. Add a local.swiftlint.ymlwithparent_config:pointing to the shared config, or use a run script. - It runs on every build, which is desirable for local development but may slow clean builds in large projects.
Command Plugin
The command plugin provides broad SwiftPM-based CLI access to SwiftLint, including --fix, --baseline, and analyze workflows that the build tool plugin cannot handle directly:
swift package plugin swiftlint
swift package plugin swiftlint --fix
swift package plugin swiftlint -- --strict --baseline .swiftlint.baseline
swift package plugin swiftlint -- analyze --compiler-log-path swift-build.logThe command plugin requires the same SwiftLintPlugins dependency. It accepts SwiftLint CLI flags after --; when using --fix, expect SwiftPM's package-directory write-permission handling because fixes can modify source files.
Xcode Run Script Build Phase
Use a run script when the build tool plugin is impractical for your project shape or when you need CLI features the build tool plugin cannot provide (for example --fix locally or --baseline). Xcode projects can still use the build tool plugin via Xcode Package Dependency even without a local Package.swift.
Basic run script
- Select the target → Build Phases → + → New Run Script Phase
- Move the phase after Compile Sources — SwiftLint is designed to analyze valid, compilable source code; linting before compilation leads to confusing results
- Add the script:
if command -v swiftlint >/dev/null 2>&1; then
swiftlint
else
echo "warning: SwiftLint not installed. Install with: brew install swiftlint"
fiOn Apple Silicon with Homebrew, swiftlint is often installed at /opt/homebrew/bin/swiftlint. If the run script cannot find it, either export that path in the build phase or create a symlink into /usr/local/bin:
if [[ "$(uname -m)" == arm64 ]]; then
export PATH="/opt/homebrew/bin:$PATH"
fi
if command -v swiftlint >/dev/null 2>&1; then
swiftlint
else
echo "warning: SwiftLint not installed. Install with: brew install swiftlint"
fiRun script with script input files (Xcode 15+)
Xcode 15 sandboxes run scripts by default. If SwiftLint fails with Sandbox: swiftlint ... deny(1) file-read-data, set ENABLE_USER_SCRIPT_SANDBOXING = NO for the target. Input files and input file lists are a separate optimization for limiting which files are linted.
- Set
ENABLE_USER_SCRIPT_SANDBOXING = NOfor the target if the script cannot read source files under Xcode 15+ - Under the run script phase, check Based on dependency analysis
- Add either explicit Input Files or readable
.xcfilelistpaths under Input File Lists - Use
--use-script-input-file-listswhen Xcode is providing.xcfilelistpaths:
if command -v swiftlint >/dev/null 2>&1; then
swiftlint --use-script-input-file-lists
fiThis requires Xcode to populate SCRIPT_INPUT_FILE_LIST_COUNT and SCRIPT_INPUT_FILE_LIST_n with readable .xcfilelist paths. Use --use-script-input-files only when Xcode is populating SCRIPT_INPUT_FILE_COUNT and SCRIPT_INPUT_FILE_n directly via Input Files rather than Input File Lists.
Alternatively, to lint the full target without file lists:
if command -v swiftlint >/dev/null 2>&1; then
swiftlint lint --config "${SRCROOT}/.swiftlint.yml"
fiAnd uncheck Based on dependency analysis if you want it to run every build.
CocoaPods run script
"${PODS_ROOT}/SwiftLint/swiftlint"CI Recipes
GitHub Actions
name: SwiftLint
on:
pull_request:
paths: ['**/*.swift']
jobs:
lint:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Install SwiftLint
run: brew install swiftlint
- name: Lint
run: swiftlint --strict --reporter github-actions-loggingFor SARIF upload to GitHub code scanning:
- name: Lint (SARIF)
run: swiftlint --strict --reporter sarif > swiftlint.sarif
continue-on-error: true
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: swiftlint.sarifGitHub Actions with baseline
- name: Lint (baseline)
run: swiftlint --strict --baseline .swiftlint.baseline --reporter github-actions-loggingGitLab CI
swiftlint:
image: ghcr.io/realm/swiftlint:latest
script:
- swiftlint --strict --reporter codeclimate > swiftlint.json
artifacts:
reports:
codequality: swiftlint.jsonIf you want GitLab JUnit-style output instead, use --reporter gitlab and publish the result as a JUnit artifact rather than codequality.
Bitrise / other CI
brew install swiftlint
swiftlint --strictReporter summary
| Reporter | Format | Best for |
|---|---|---|
xcode |
Xcode-compatible text | Local builds, Xcode run scripts |
github-actions-logging |
GitHub Actions annotations | PR inline comments |
sarif |
SARIF JSON | GitHub code scanning |
json |
JSON array | Custom tooling, dashboards |
checkstyle |
XML | Jenkins, SonarQube |
csv |
CSV | Spreadsheet analysis |
emoji |
Text with emoji | Fun terminal output |
Working With Multiple Swift Versions
SwiftLint is predominantly SwiftSyntax-based, but some rules still rely on SourceKit/Clang for additional analysis. It must remain compatible with the Swift toolchain used to compile your project.
Key rules:
- Run SwiftLint with the same Swift toolchain used to build your project.
- On macOS, SwiftLint resolves the toolchain in this order:
XCODE_DEFAULT_TOOLCHAIN_OVERRIDE,TOOLCHAIN_DIRorTOOLCHAINS,xcrun -find swift,/Applications/Xcode.app/...,/Applications/Xcode-beta.app/...,~/Applications/Xcode.app/...,~/Applications/Xcode-beta.app/.... - In CI with multiple Xcode versions, set
DEVELOPER_DIRbefore running SwiftLint:
export DEVELOPER_DIR=/Applications/Xcode_16.app/Contents/Developer
swiftlint- The build tool plugin automatically uses the correct toolchain because it runs within the build system.
- Homebrew-installed SwiftLint may lag behind the latest Swift release. If you see parsing errors after updating Xcode, check for a SwiftLint update.
sourcekitd.frameworkis expected in the selected toolchain’susr/lib/directory. Toolchain mismatches typically show up as parsing or SourceKit failures.
VS Code
The SwiftLint VS Code extension runs SwiftLint on save.
// .vscode/settings.json
{
"swiftlint.enable": true,
"swiftlint.path": "/opt/homebrew/bin/swiftlint",
"swiftlint.autoLintWorkspace": false
}Fastlane
# Fastfile
lane :lint do
swiftlint(
mode: :lint,
config_file: ".swiftlint.yml",
strict: true,
raise_if_swiftlint_error: true
)
endDocker
The official SwiftLint Docker image is useful for Linux CI:
docker run --rm -v "$(pwd):/work" -w /work ghcr.io/realm/swiftlint:<reviewed-version> --strictPre-commit Hook
Using the pre-commit framework
# .pre-commit-config.yaml
repos:
- repo: https://github.com/realm/SwiftLint
rev: <reviewed-version>
hooks:
- id: swiftlintTo apply fixes and fail on warnings/errors from the hook, use an entry override:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/realm/SwiftLint
rev: <reviewed-version>
hooks:
- id: swiftlint
entry: swiftlint --fix --strictManual git hook
#!/bin/sh
# .git/hooks/pre-commit
if command -v swiftlint >/dev/null 2>&1; then
git diff --cached --name-only --diff-filter=d -- '*.swift' | \
xargs -I{} swiftlint lint --path "{}" --strict --quiet
fiMake it executable: chmod +x .git/hooks/pre-commit