🎨

Server-Side SwiftでSwiftLintのpluginとSwift buildを両立する方法

2024/09/18に公開

概要

SwiftのLinterやFormatterといえばSwiftLintが有名ですが、残念ながらこのライブラリはLinuxに対応していません。正確に言うと、Linux上でLintを行うことはできますが、plugin付きでのLinux上でのビルドはできません。

この記事では、SwiftLintをpluginとして使用し、GitHub ActionsでCIを回しつつ、Linux向けのビルド時にはSwiftLintを除外する方法を紹介します。

結論

Package.swift
// swift-tools-version:5.10
+import Foundation
import PackageDescription

let package = Package(
    name: "api",
    platforms: [
       .macOS(.v14)
    ],
    dependencies: [
        // 💧 A server-side Swift web framework.
        .package(url: "https://github.com/vapor/vapor.git", from: "4.99.3"),
        // 🗄 An ORM for SQL and NoSQL databases.
        .package(url: "https://github.com/vapor/fluent.git", from: "4.9.0"),
        // 🐘 Fluent driver for Postgres.
        .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.8.0"),
        // 🔵 Non-blocking, event-driven networking for Swift. Used for custom executors
        .package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
        // SwiftLint
+       .package(url: "https://github.com/realm/SwiftLint.git", branch: "main"),
    ],
    targets: [
        .executableTarget(
            name: "App",
            dependencies: [
                .product(name: "Fluent", package: "fluent"),
                .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
                .product(name: "Vapor", package: "vapor"),
                .product(name: "NIOCore", package: "swift-nio"),
                .product(name: "NIOPosix", package: "swift-nio"),
            ],
            swiftSettings: swiftSettings,
+           plugins: swiftLintPlugins
        ),
        .testTarget(
            name: "AppTests",
            dependencies: [
                .target(name: "App"),
                .product(name: "XCTVapor", package: "vapor"),
            ],
            swiftSettings: swiftSettings
        ),
    ]
)

var swiftSettings: [SwiftSetting] { [
    .enableUpcomingFeature("DisableOutwardActorInference"),
    .enableExperimentalFeature("StrictConcurrency"),
] }

+var swiftLintPlugins: [Target.PluginUsage] {
+   guard Environment.enableSwiftLint else { return [] }
+   return [
+       .plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLint")
+   ]
+}

+enum Environment {
+   static func get(_ key: String) -> String? {
+       ProcessInfo.processInfo.environment[key]
+   }
+   static var enableSwiftLint: Bool {
+       Self.get("SWIFTLINT") == "true"
+   }
+}

これは自分の好みのルールです。

.swiftlint.yml
opt_in_rules:
  - array_init
  - closure_spacing
  - collection_alignment
  - comma_inheritance
  - contains_over_filter_count
  - contains_over_filter_is_empty
  - contains_over_first_not_nil
  - contains_over_range_nil_comparison
  - direct_return
  - discouraged_assert
  - discouraged_optional_boolean
  - discouraged_optional_collection
  - empty_collection_literal
  - empty_count
  - empty_string
  - enum_case_associated_values_count
  - expiring_todo
  - explicit_init
  - fallthrough
  - fatal_error_message
  - file_name_no_space
  - first_where
  - flatmap_over_map_reduce
  - joined_default_parameter
  - last_where
  - multiline_arguments
  - multiline_arguments_brackets
  - period_spacing
  - prefer_self_in_static_references
  - prefer_self_type_over_type_of_self
  - redundant_nil_coalescing
  - redundant_self_in_closure
  - redundant_type_annotation
  - return_value_from_void_function
  - sorted_imports
  - static_operator
  - toggle_bool
  - unneeded_parentheses_in_closure_argument
  - vertical_parameter_alignment_on_call
trailing_comma:
  mandatory_comma: true
identifier_name:
  min_length:
    warning: 1
    error: 1
  validates_start_with_lowercase: off
type_name:
  min_length:
    warning: 1
    error: 1
  validates_start_with_lowercase: off
nesting:
  type_level:
    warning: 3
    error: 5
disabled_rules:
  - non_optional_string_data_conversion
analyzer_rules:
  - unused_declaration
  - capture_variable
  - unused_import
included:
  - Sources
  - Tests
  - Package.swift
strict: true
line_length: 120
reporter: "xcode"
.github/workflows/swiftlint.yaml
name: SwiftLint

on:
  pull_request:
    paths:
      - '.github/workflows/swiftlint.yaml'
      - '.swiftlint.yml'
      - './**/*.swift'

jobs:
  SwiftLint:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4

      - name: GitHub Action for SwiftLint (Only files changed in the PR)
        uses: norio-nomura/action-swiftlint@3.2.1
        with:
          args: --strict
        env:
          SWIFTLINT: true

まとめ

実はPackage.swiftの中でも環境変数で設定を動的に変更することができるというお話でした。
なお、.envファイル自体は読み込まれないため、開発環境ではXcodeのスキーマ設定で環境変数を設定してください。それでもできなければターミナルで環境変数を設定してからXcodeを開き直してみてください。

nextbeat Tech Blog

Discussion