さて皆さん、dependabotをご存知でしょうか
GitHub Advanced Security · Built-in protection for every repository
Fix vulnerabilities and safeguard your software supply chain with built-in, AI-powered security.
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の場合は試しにこのように設定しました。ここのオプションについては↓にドキュメントへのリンクを貼っておくのでそれを参考にすればいいでしょう
Dependabot options reference - 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から導入してみましょう