さて皆さん、dependabotをご存知でしょうか
GitHub Security
Build on a secure foundation.
dependabot.com
Node.jsやRubyのプロジェクトをGitHubで管理している人なら、ある日唐突に使ってるライブラリーの脆弱性が発見されたからバージョンアップしろよな!PRを出してくるあのbotです
本来の用途としてはライブラリーのアップデートがあったらアップデートするPullRequestを自動で作成してくれるbotです。それがGitHubに買収かなにかされたらしくGitHubとの統合が進んでいろいろと便利にしてくれてるみたいな感じです
さて、そのdependabotですが現在様々な言語やプラットフォームに対応していたり対応途中だったりします。gradleも対応中のプラットフォームということでBETA版になっています
そして、public・private repository問わずに無料で使えるみたいです(private repositoryの場合はGitHubへデータの提供を許可しないといけないです)
というわけでこれを使わないわけにはいかないですよね?使っていきましょう
Setup
GitHubのRepository上でInsightsのDependency graphのところからdependabotを有効にすることができます。ここのEnableボタンを押すと.github/dependabot.yml
を作成することになります。この.github/dependabot.yml
がdependabotの設定なのでここを各々好きなように設定すればいいのです
version: 2
updates:
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "daily"
reviewers:
- "MeilCli"
open-pull-requests-limit: 20
MeilCliの場合は試しにこのように設定しました。ここのオプションについては↓にドキュメントへのリンクを貼っておくのでそれを参考にすればいいでしょう
Configuration options for the dependabot.yml file - GitHub Docs
Detailed information for all the options you can use to customize how Dependabot maintains your repositories.
help.github.com
.github/dependabot.yml
をコミットすれば初回としてdependabotが作動します。それ以降はschedule
に設定したとおりに自動で動作するようになります
PullRequest
dependabotが作成してくれたPullRequestはbase branchが更新されたら自動でrebaseしてくれます。その他にもコメントで@dependabot
にコマンドを送り付けると様々なアクションを取ってくれます
コマンド一覧を見る限りは実運用に耐えれそうな機能を持っている様子でした
Advanced
さてdependabotですが、ソースコードを読む限りはGradleプロジェクトをビルドしているわけではなさそうです
自分が読み取れた限りでは以下のように動作しているようです
settings.gradle
からモジュール情報を抜き取る- 抜き取ったモジュール情報からモジュールの
build.gradle
を探し出す - 見つけた
build.gradle
から正規表現などでMaven Repository URLと依存の宣言(example:example:1.0
みたいなやつ)を抜き取るext
などの変数定義にはできる限り対応させている様子
- あとはMaven Repository URLに対して依存のアップデートがないか確認する
このような動作をしているため、ここから外れたbuild.gradleをしているとdependabotに検知してくれません。あとbuild.gradle.kts
に対応してないのでKotlinで書いてたら問答無用で検知してくれません
自分の場合はbuildSrc
にまとめて依存情報の定義を行っていたため検知してくれませんでした
dependabotが検知してくれないので諦めるのか?いえ、そうではありません。検知してくれないのなら検知してくれるように変えればいいのです。そして正規表現で抜き出してるのならbuild.gradle
として完全体である必要もありません
さてdependabotを騙すわけですが、その構成としては画像のように行っていきます
実際に依存を定義するのはdependencies
モジュールのbuild.gradle
で行うのですが、このモジュールはこれのためだけに用意する空のモジュールにします。dependencies
モジュールを使うことによってdependabotに検知されるファイルパスにするわけです
そしてbuildSrc/build.gradle.kts
からdependencies/build.gradle
を読み取って依存の定義ファイルを生成します。ここの依存の定義ファイルを自動生成する部分に関してはちょうどよさそうなgmazo/gradle-buildconfig-pluginを使います。このPluginはAndroidモジュールで言うところのbuildConfigField
でBuildConfig
ファイルを自動生成してくれるみたいな動作をしてくれます。自動生成された依存の定義ファイルを各モジュールのbuild.gradle
でimportすればいいという仕掛けです
では、画像の構成を実現していきましょう。root直下のbuild.gradle
にgmazo/gradle-buildconfig-pluginをclasspathに通します
buildscript {
repositories {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath "com.github.gmazzo:gradle-buildconfig-plugin:2.0.1"
}
}
その次にdependencies/build.gradle
に依存を定義します
apply plugin: "com.github.gmazzo.buildconfig"
buildConfig {
packageName("net.meilcli.librarian.gradle.dependencies") // Packageは各々buildSrcのpackageに合わせて変更してくれ
className("Dependencies")
buildConfigField("String", "test", "\"test_value\"") // ここで他に必要な値を自動生成できる
}
ext {
configUrl = ""
configName = ""
}
@SuppressWarnings("GrMethodMayBeStatic")
void repositories(Closure closure) {
closure()
}
@SuppressWarnings("GrMethodMayBeStatic")
void maven(Closure closure) {
closure()
buildConfig.forClass("Repositories") {
buildConfigField("String", configName, "\"$configUrl\"")
}
ext.set("repository_$configName", configUrl)
}
@SuppressWarnings("GrMethodMayBeStatic")
void url(String url) {
ext.configUrl = url
}
@SuppressWarnings("GrMethodMayBeStatic")
void name(String name) {
ext.configName = name
}
@SuppressWarnings("GrMethodMayBeStatic")
void dependencies(Closure closure) {
closure()
}
@SuppressWarnings("GrMethodMayBeStatic")
void library(String notice, String name, String className) {
buildConfig.forClass(className) {
buildConfigField("String", name, "\"$notice\"")
}
ext.set("library_${className}_${name}", notice)
}
repositories {
// mave { の後ろにはurl ""が先に来る必要がある
maven {
url "https://maven.google.com/"
name "google"
}
maven {
url "https://jcenter.bintray.com/"
name "jcenter"
}
maven {
url "https://plugins.gradle.org/m2/"
name "gradle"
}
}
dependencies {
ext.kotlinGroup = "org.jetbrains.kotlin"
ext.kotlinVersion = "1.3.70"
library "$kotlinGroup:kotlin-gradle-plugin:$kotlinVersion", "gradle", "Kotlin"
library "$kotlinGroup:kotlin-stdlib-jdk7:$kotlinVersion", "stdlib", "Kotlin"
library "$kotlinGroup:kotlin-serialization:$kotlinVersion", "gradle", "KotlinSerialization"
library "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0", "runtime", "KotlinSerialization"
}
そしてこのファイルをbuildSrc/build.gradle.kts
で利用すればいいわけです
plugins {
`kotlin-dsl`
}
apply(from = "../dependencies/build.gradle")
buildscript {
repositories {
maven("https://plugins.gradle.org/m2/")
}
dependencies {
classpath("com.github.gmazzo:gradle-buildconfig-plugin:2.0.1")
}
}
あとはgradle-buildconfig-pluginが定義ファイルを自動生成してくれるので各々使いたいところでimportすればいいです
ただ、buildSrc/build.gradle.kts
だけはブートストラップ問題により以下の制限がかかります
buildscript
ブロックでは定義情報を参照できない- それ以外では
ext(ktsだとextra)
から参照する- たとえば先ほどの例だと
val kotlinGradle = extra["library_Kotlin_gradle"] as String
のようにして参照します
- たとえば先ほどの例だと
その他細かいところは↓のリポジトリーで実際に運用しているのでそちらのコードを読んでください
GitHub - MeilCli/Librarian: Librarian is generate notice that library used in gradle module
Librarian is generate notice that library used in gradle module - MeilCli/Librarian
github.com
最後に
dependabotを活用したらライブラリー管理が非常に楽になると思いますが、PullRequestが自動で作られてもそのPullRequestがビルドに成功するかとかテストに成功するかとかを自動でチェックするCIがなければあまり労力は変わらないでしょう。なのでdependabotを導入するか悩んでいてCIによる自動化が不完全なリポジトリーではまずCIから導入してみましょう