Now in REALITY Tech #36 Peripheryを用いて REALITY iOSの未使用コードを自動検知した話
今週の「Now in REALITY Tech」は、最近追加したPeripheryでのiOSプロジェクトの静的解析とCI導入についてiOSチームからお送りします。
Peripheryとは
Peripheryは静的解析によりSwiftプロジェクトから未使用のコードを検出するツールです。
Homebrew、Swift Package Manager、Cocoapodsのpackage managerに対応しており簡易に導入可能となっています。
検出可能なコード
上記の通り宣言されたclassやstruct、プロパティなどが検出可能で、他にも未使用のメソッド引数や不必要なアクセスレベルなどの高度な解析も可能となっています。
使い方
Peripheryで利用可能なコマンドは下記の通りで、解析には scan を使用します。
(※ Swift Package ManagerでPeripheryをinstallしています
❯ swift run periphery --help
USAGE: periphery <subcommand>
OPTIONS:
-h, --help Show help information.
SUBCOMMANDS:
scan Scan for unused code
check-update Check for available update
version Display this version of Periphery
scan コマンドで利用可能なオプションは下記の通りで、対象のTargetや解析対象から除外したいファイルの指定などが可能です。
❯ swift run periphery scan --help
OVERVIEW: Scan for unused code
USAGE: periphery scan [<options>] [<build-arguments> ...]
ARGUMENTS:
<build-arguments> Arguments following '--' will be passed to the underlying build tool, which is either 'swift build' or 'xcodebuild'
depending on your project
OPTIONS:
--setup Enable guided setup
--config <config> Path to configuration file. By default Periphery will look for .periphery.yml in the current directory
--workspace <workspace> Path to your project's .xcworkspace. Xcode projects only
--project <project> Path to your project's .xcodeproj - supply this option if your project doesn't have an .xcworkspace. Xcode projects
only
--schemes <schemes> Comma-separated list of schemes that must be built in order to produce the targets passed to the --targets option.
Xcode projects only (default: [])
--targets <targets> Comma-separated list of target names to scan. Required for Xcode projects. Optional for Swift Package Manager
projects, default behavior is to scan all targets defined in Package.swift (default: [])
--format <format> Output format (allowed: xcode, csv, json, checkstyle) (default: xcode)
--index-exclude <index-exclude>
Path glob of source files which should be excluded from indexing. Declarations and references within these files
will not be considered during analysis. Multiple globs may be delimited by a pipe (default: [])
--report-exclude <report-exclude>
Path glob of source files which should be excluded from the results. Note that this option is purely cosmetic, these
files will still be indexed. Multiple globs may be delimited by a pipe (default: [])
--index-store-path <index-store-path>
Path to index store to use. Implies '--skip-build'
--retain-public Retain all public declarations - you'll likely want to enable this if you're scanning a framework/library project
--disable-redundant-public-analysis
Disable identification of redundant public accessibility
--retain-assign-only-properties
Retain properties that are assigned, but never used
--retain-assign-only-property-types <retain-assign-only-property-types>
Comma-separated list of property types to retain if the property is assigned, but never read (default: [])
--external-encodable-protocols <external-encodable-protocols>
Comma-separated list of external protocols that inherit Encodable. Properties of types conforming to these protocols
will be retained (default: [])
--retain-objc-accessible
Retain declarations that are exposed to Objective-C implicitly by inheriting NSObject classes, or explicitly with
the @objc and @objcMembers attributes
--retain-unused-protocol-func-params
Retain unused protocol function parameters, even if the parameter is unused in all conforming functions
--clean-build Clean existing build artifacts before building
--skip-build Skip the project build step
--strict Exit with non-zero status if any unused code is found
--disable-update-check Disable checking for updates
--verbose Enable verbose logging
--quiet Only output results
-h, --help Show help information.
例えば、下記のコマンドを実行することで R.generated.swift ファイルを解析から除外し、アクセス修飾子が public である必要がないファイルを許容する解析となります。
❯ swift run periphery scan --project プロジェクト名 --targets ターゲット名 --schemes スキーム名 --index-exclude "src/R.generated.swift" --disable-redundant-public-analysis
導入例
ローカル環境+Xcodeで解析
上記はターミナルアプリからCLIで実行する例を紹介しましたが、ターミナルの出力内容だと解析結果がわかりにくいため、Xcode上でwarningとして解析結果を可視化し、対象のコードにジャンプ出来るようにします。
導入方法はPeripheryのリポジトリのREADMEで詳細に記載されているのでここでは割愛します。
GitHub Pull Request で解析
ローカル環境+Xcodeで解析する方法では、プロジェクトの全コードを対象としていましたが、大きなプロジェクトなどにおいては小さく導入したいケースがあるでしょう。
そのようなケースではdanger-peripheryを使用することで、GitHubのPull Request(以下、PR)で発生した差分のみを対象に未使用コードの検知およびPRへのコメントが可能です。
下記は実際にdangerをbitriseで実行し、検出した内容です。
REALITY iOSでのPeripheryの運用
上記で紹介したGitHub PRへの導入は、REALITY iOSでは採用しませんでした。理由としては、Peripheryの解析時間が長いためCIのボトルネックとなるためです。
そのため、我々はBitriseのscheduling buildを使用して1日に1回深夜に実行し、実行結果をslackに通知するようにしました。
具体的には、リリース済みの最新バージョンのコードと現在開発中(develop branch)との差分があるファイルから、peripheryでscanした結果をslack通知するようにしています。
この方法により、未使用コードの自動検知をしつつ日々の開発に影響を与えないようにしています。
// Makefile
periphery:
xcrun --sdk macosx swift run periphery scan --workspace "ワークスペース名" --schemes "スキーム名" --targets "ターゲット名" --index-exclude "**/R.generated.swift|**/*.{xib,storyboard}" --quiet --disable-redundant-public-analysis > scanned.txt // 解析
VERSION=${VERSION} sh script/periphery/filter.sh // 対象versionとの差分を検出
VERSION=${VERSION} sh script/periphery/slack_notify.sh // slack通知
// filter.sh
diff_files=$(git diff v${VERSION} --diff-filter=d --name-only -- "*.swift" -- ':(exclude)vLive-viewer-iosTests/*')
touch periphery_report.txt
for filename in ${diff_files[@]}; do
grep $filename scanned.txt >> periphery_report.txt
done
exit 0
まとめ
今回はPeripheryを用いた未使用コードの自動検知の導入を紹介しました。
開発しているプロジェクトの規模によって、導入方法を検討する必要がありますが、コードが不必要に増えて運用保守コストが上がる前の対策としては導入しがいがありそうです。
また、danger-peripheryを導入するとGitHub PRへのレビューコスト削減にも繋がるので検討してみてはいかがでしょうか?
REALITY では一緒に開発してくれるメンバーを大募集しています! まずは気軽にカジュアル面談も受け付けていますので、お気軽にお申し込みください!