Gradle で Single-Project から Multi-Project への変換

適当に single project で始めた後に事業拡張等で複数のアプリケーションが必要になったり共通化したりしたくなった場合にどのような作業を行うといいかというの検討します。

バージョン情報

  • Gradle 6.5

Gradle で Single-Project から Multi-Project への変換

Gradle での Multi Project とは?

gradle がそのプロジェクトが single か multi かを判別するのは ビルドライフサイクルでの3つあるフェーズのうちの initialize phase のときです1

そしてこれは settings.gradle(.kts) ファイルを見つけ出し、 include で自身以外のプロジェクトが追加されているかどうかで決まります。 Multi Project に含まれるプロジェクトは Root Project や Sub Project と呼ばれます2

Single Project というのはつまり Multi Project における root project をそのまま利用しているという状況なので、これを include で指定した場所にそのまま移動させて sub project として追加できると良さそうです。

Single-Project の開発

例として以下のようなプロジェクトを作成します。build script を Kotlin DSL で記述してありますがここで行う作業では Groovy DSL でも大きくは変わりません。

❯ gradle init --type basic --dsl kotlin --project-name single-to-multi-project

> Task :init
Get more help with your project: https://guides.gradle.org/creating-new-gradle-builds

BUILD SUCCESSFUL in 517ms
2 actionable tasks: 2 executed

以下のコマンドでプロジェクトが root project のみであることが確認できます。

❯ ./gradlew projects

> Task :projects

------------------------------------------------------------
Root project
------------------------------------------------------------

Root project 'single-to-multi-project'
No sub-projects

To see a list of the tasks of a project, run gradlew <project-path>:tasks
For example, try running gradlew :tasks

BUILD SUCCESSFUL in 458ms
1 actionable task: 1 executed

ある程度開発したとして build.grad.kts を以下のようなものになったとします。

plugins {
  java
  id("org.springframework.boot")
  id("io.spring.dependency-management")
}

repositories {
    mavenCentral()
}

dependencies {
  implementation("org.springframework.boot:spring-boot-starter-web")
}

settings.gradle.kts

pluginManagement {
    repositories {
        gradlePluginPortal()
    }

    plugins {
        id("org.springframework.boot") version "2.3.0.RELEASE"
        id("io.spring.dependency-management") version "1.0.9.RELEASE"
    }
}

rootProject.name = "single-to-multi-project"

Single-Project から Multi-Project へ

以下のようにして sub project を作ってみます。 このとき、実体となる build script はこの時点では必要ありません。

❯ echo 'include("single-to-multi-project")' >> settings.gradle.kts 
❯ ./gradlew projects                                    

> Task :projects

------------------------------------------------------------
Root project
------------------------------------------------------------

Root project 'single-to-multi-project'
\--- Project ':single-to-multi-project'

To see a list of the tasks of a project, run gradlew <project-path>:tasks
For example, try running gradlew :single-to-multi-project:tasks

BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed

ちなみにこの時点での構成は下記であるとします。

❯ tree
.
├── build.gradle.kts
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
    └── main
        └── java
            └── com
                └── example
                    └── ExampleApplication.java

このとき、gradle build を実行すると build/libs 以下に single-to-multi-project.jar という jar が生成されます。

❯ ./gradlew build

BUILD SUCCESSFUL in 769ms
2 actionable tasks: 2 executed

❯ tree build
 build
 ├── classes
 │   └── java
 │       └── main
 │           └── com
 │               └── example
 │                   └── ExampleApplication.class
 ├── generated
 │   └── sources
 │       ├── annotationProcessor
 │       │   └── java
 │       │       └── main
 │       └── headers
 │           └── java
 │               └── main
 ├── libs
 │   └── single-to-multi-project.jar
 └── tmp
     ├── bootJar
     │   └── MANIFEST.MF
     └── compileJava
         └── source-classes-mapping.txt

さてここで本題となる、sub project に中身も移動して multi project を完成させましょう。

❯ mkdir single-to-multi-project
❯ mv build.gradle.kts single-to-multi-project
❯ tree
.
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── single-to-multi-project
    ├── build.gradle.kts
    └── src
        └── main
            └── java
                └── com
                    └── example
                        └── ExampleApplication.java

8 directories, 7 files

以下のコマンドでビルドしてみます。

いずれの場合であっても subproject 側の build ディレクトリに jar ファイルが生成されていると思います。

❯ ./gradlew build
or
❯ ./gradlew :single-to-multi-project:build

❯ tree single-to-multi-project/build 
single-to-multi-project/build
├── classes
│   └── java
│       └── main
│           └── com
│               └── example
│                   └── ExampleApplication.class
├── generated
│   └── sources
│       ├── annotationProcessor
│       │   └── java
│       │       └── main
│       └── headers
│           └── java
│               └── main
├── libs
│   └── single-to-multi-project.jar
└── tmp
    ├── bootJar
    │   └── MANIFEST.MF
    └── compileJava
        └── source-classes-mapping.txt

ここまでで一旦 multi project に変換するまでを行いました。multi project の場合の共通処理の書き方やビルドパフォーマンスを上げる方法などいくつか話題はありますが次回以降の話題とします。

Single-Project から Multi-Project へ(別解)

ある project と他の project を関連させながら開発を行う方法として gradle の Composite Build3 という仕組みがあります。 これは厳密には Multi Project ではないため、Gradleドキュメント上で Multi Project と記載されているものと混同しないように注意してください。 この方法のメリットとしては、元のプロジェクトに全く手を加えることなく他のプロジェクトを作成し、依存として利用することができます。

さて、「Single-Project の開発」からの続きであるとして、その次のステップとして root project に相当する project を作成します。これは元のプロジェクトとは別のプロジェクトとして作成します。 注意点としては、このプロジェクトは 現在の gradle バージョンにおいては composite build の制限により同名のプロジェクト名を利用できません

❯ gradle init --type basic --dsl kotlin --project-name multi-project 

> Task :init
Get more help with your project: https://guides.gradle.org/creating-new-gradle-builds

BUILD SUCCESSFUL in 492ms
2 actionable tasks: 2 executed

他のプロジェクトと連携したい project を multi-project に追加します。

❯ echo 'includeBuild("single-to-multi-project")' >> settings.gradle.kts
❯ mv path/to/single-to-multi-project .

build.gradle.kts に以下のような記述を追加します(現在の gradle の制限により(まだ)直接コマンドラインからは起動できないため4 )。

tasks.register("build") {
    dependsOn(gradle.includedBuild("single-to-multi-project").task(":build"))
}
❯ ./gradlew build                                           

BUILD SUCCESSFUL in 1s

対象プロジェクトの jar ができていることが確認できます。

❯ tree single-to-multi-project/build/
single-to-multi-project/build/
├── classes
│   └── java
│       └── main
│           └── com
│               └── example
│                   └── ExampleApplication.class
├── generated
│   └── sources
│       ├── annotationProcessor
│       │   └── java
│       │       └── main
│       └── headers
│           └── java
│               └── main
├── libs
│   └── single-to-multi-project.jar
└── tmp
    ├── bootJar
    │   └── MANIFEST.MF
    └── compileJava
        └── source-classes-mapping.txt

ここまでで一旦 multi project に変換、 というより他のプロジェクトからビルドするまでを行いました。

このまま他のプロジェクトを追加する際の注意点などはいろいろありますが、構成や制限などがいろいろありややこしいため別途 composite build として話題とするか ドキュメント5 を参照してください。

以上です。

gradle wrapper の jar バイナリファイルが正しいものかどうかを確認する

gradle wrapper の jar を更新した時、 jar ファイルが更新されたりされなかったりして正しいかどうかがよくわからない場合がある。

手元で確認する場合は以下のように実行すると良い。(gradle 6.1.1 の場合)

$ cd gradle/wrapper
$ curl --location --output gradle-wrapper.jar.sha256 https://services.gradle.org/distributions/gradle-6.1.1-wrapper.jar.sha256
$ echo "  gradle-wrapper.jar" >> gradle-wrapper.jar.sha256
$ shasum --check gradle-wrapper.jar.sha256

本来のモチベーションとしては OSS 上で Gradle バージョンアップおじさんにもらう Pull Request で妙な binary を入れられてもわかるようにするというような目的の様子。

https://docs.gradle.org/6.1.1/userguide/gradle_wrapper.html#wrapper_checksum_verification

Verifying Gradle Wrappers with GitHub Actions

Gradle で optional のようなものを実現するには

バージョン情報

  • Gradle 6.0.1
  • propdeps plugin 0.0.9
  • maven 3.6.2

一言で言うと

featureVariants を使う。

詳しく言うと

Spring Boot などで auto configuration を実装しようとする場合など、対象のサポートをするが必須ではないライブラリを使うようにしたい場合がある*1

maven の場合はoptionalタグを true にすることで、開発しているライブラリ側では利用することができるがそのライブラリを利用する側では無視される*2

gradle の場合は方法としては 2 つ

それぞれ違いがあるが適当にかくと以下のような感じ。

方法 pom.xml 出力 plugin artifact 出力タスク 備考
propdeps plugin を使う maven uploadArchives gradle 6.0 以降では maven plugin を用いた publish は非推奨
featureVariants を使う maven-publish publish maven plugin では optional を出力できない

(最近はまだ gradle module metadata はあまり使われている気配はないので割愛)

propdeps plugin について

propdeps pluguin は spring team が作っている gradle plugin。 optional configuration を追加し、 maven での optional のような動きをするようにする。

挙動としては compile configuration の classpathoptional configuration を追加する。挙動としては compileOnly とほぼ同じ気はする。 ライブラリとして publish する場合には propdeps-maven plugin を利用し、 <scope>optional</scope> という(maven の仕様にはない) scope を一旦追加したあと gradle core apiuploadArchives task を用いる場合に <scope>compile</scope><optional>true</optional> に差し替えて pom.xml を出力する。ちなみに maven plugin にしか対応してないので maven-publish plugin で publish task を実行した場合 は <scope>optional</scope> で pom.xml が出力されてしまい思ったように動かなくなるかもしれない。

使うべきかどうかについては、おそらく新規では採用しない方がよさそう。というのも propdeps plugin はしばらく更新もされておらず(2017 年最終)、publish に使用する maven plugin が gradle 6.0 以上で deprecated, gradle 7.0 以上で 削除予定なのであまり利用しない方がよさそう*3。中で使ってるgradle のバージョンも 3.3 でだいぶ古い*4

featureVariants について

Gradle 5.3 から導入されている機能*5。詳しい説明は Modeling feature variants and optional dependencies を参照。

ドキュメントにも以下のように記載がある。

a (better) substitute for Maven optional dependencies

自分の理解としては 1つの componentvariantsimplementationruntime 等)を跨いで何らかの利用するかどうかを選択できる機能を実現する場合などに利用する*6

local component*7 や gradle module metadata としてそのライブラリを依存に含めて利用する場合は capability として名前をつけていくつかの依存をまとめて 依存に含めることができたりする。そうでない maven や ivy(コチラはよく知らない)などの external component はそれらに対応する機能に変換されて出力され、 maven-publish plugin の場合は その依存モジュールに <optional>true</optional> が付与される*8

ちなみに Modeling feature variants and optional dependencies には 以下のように記載があるが pom.xml には classifier タグとして出力される気配がないのでよくわからない。

using POM metadata (Maven), feature variants are published as optional dependencies and artifacts of feature variants are published with different classifiers

gradle 単体で運用してるならば capability の指定(capability の命名規則はデフォルトでは group:artifactId-(feature name の kebab case))により意味のある名前でまとめて 依存追加できるし maven repo への publish もサポートしてるので こちらの方がよさそうに見える。

2019 年まとめ

技術編

  • コードを少し書いた
  • ひたすらコードレビューをしていた
  • 運用 VPNOpenVPN から WireGuard に変更した
  • Spring Boot に比較的詳しくなった
  • Gradle に比較的詳しくなった
  • VPSlinode に集め始めた
  • Unity を少し書いた
  • 電子工作をすこし学んだ

ゲーム編

2019 年に購入または 2019 年にプレイした形跡があるもの

マンガ編

  • 1392 冊買った

家編

  • ブレーカーの電流を上げる契約をした
  • マイナンバーカードを作った
  • SoftbankAir を解約した
  • MacBook Pro を買った
  • Surface Laptop 3 を買った
  • G Suite を利用し始めた

コーヒー編

旅行編

  • 国内(出張込み)
    • 東京
      • ディズニーシー
    • 福岡
    • 大阪
    • 静岡
    • 名古屋
    • 神戸
    • 有馬温泉
    • 日光
  • 国外
    • 台湾

Gradle で Maven BOM を作る

概要

Gradle で複数のプロジェクトでバージョンを共通なものにしたい場合などのために BOM を作成する。

前提

ここでは下記バージョンをもとにしたドキュメントやソースを参照している

BOM とは

"bill of materials" 、つまり部品表の略。(あるいは "build of materials" と記載されている例もある) アーティファクトを列挙し、その BOM を利用する際に依存するバージョンを統一することができる。

maven.apache.org

BOM の例

Spring Boot Dependencies

Spring Boot の BOM, 基本的には Spring Boot で開発する場合はこれが使われており、 Spring Boot 内で使われているものと同じバーションのものをアプリケーション側でも利用することが多いと思う。

Spring Boot の依存関係は別途 HTML ドキュメントとして出力されており、下記のようにドキュメントのページで確認できる。

軽く触ったりする場合などは明示的に使っているパターンは少なくて、通常は下記のような Gradle Plugin が自動的に Spring Boot Dependencies を import してくれている。

Spring Framework BOM/Spring Security BOM

Spring Framework の BOM。基本的には リリースされるモジュールのバージョンを一括で登録してある BOM となっているようにみえる。

Spring Boot Dependencies にも利用されている。

spring-boot/pom.xml at v2.2.0.M4 · spring-projects/spring-boot · GitHub

JUnit BOM

JUnit の BOM

AWS Java SDK BOM

AWS Java SDK の BOM

Gradle で BOM を作成する

Magen の pom.xml ではそのまま DepenendencyManagement セクションにアーティファクト情報を記載すれば良い。

gradle の場合は方法は確認した限りでは2つ。

  • java-platform-plugin を利用する
  • spring team の DependencyManagment plugin を利用する

java-platform-plugin を利用する

docs.gradle.org

dependencies 以下に constrains として追加する。おそらく基本的には api を用いると思う(理由は未調査)

dependencies {
    constrains {
        api 'org.springframework:spring-core:4.0.3.RELEASE'
    }
}

scope import の場合は dependendy として追加する。 java-platform plugin はデフォルトでは dependencies は許可されてないので別途 許可する設定を追加する。

javaPlatform {
    allowDependencies()
}

dependencies {
    api platform('io.spring.platform:platform-bom:1.0.1.RELEASE')
}

publish する場合は maven-publish plugin を利用して以下のように記述する。

publishing {
    publications {
        maven(MavenPublication) {
            from components.javaPlatform
        }
    }
}

spring team の DependencyManagment plugin を利用する

Gradle の場合は 依存性の記載には spring チームが出している Dependency Management Plugin を利用する。 これを利用することによって MavenDependencyManagement セクションで行うような記載ができるようになる。

plugins.gradle.org

docs.spring.io

dependencyManagement {
    dependencies {
        dependency 'org.springframework:spring-core:4.0.3.RELEASE'
    }
}

scope import の場合

dependencyManagement {
    imports {
        mavenBom 'io.spring.platform:platform-bom:1.0.1.RELEASE'
    }
}

dependencies には下記のようにバージョン情報を抜いて記載できる。この場合は dependencyManagement に記載のある 4.0.3.RELEASE が利用される。

dependencies {
  implementation 'org.springframework:spring-core'
}

ここまではまあ通常の用途で、その記載対象となるプロジェクト下でのバージョン情報や、 multiproject などで sub project でのバージョン情報などを省略できる。

他の独立したプロジェクト(別リポジトリ等)で利用したい場合は その dependencyManagement 情報を publish する必要がある。このとき必要なのが maven-publich プラグインである。(maven リポジトリの場合) これを使うことで gradle のプラグインで記載された dependencyManagement を pom.xml で出力し、 maven リポジトリを通じてその他のプロジェクトで利用できる形にできる。

Maven Publish Plugin

publishing {
    publications {
        maven(MavenPublication) {
        }
    }
}

例: Gradle scripts to generate a BOM and then consume that BOM · GitHub

また pom.xml 側で記載されているような properties を記載したい場合には以下のように書く。

publishing {
    publications {
        maven(MavenPublication) {
            pom {
                properties['spring.boot.version'] = '2.2.0.M4'
            }
        }
    }
}

ローカルで使って見る場合には publishToMavenLocal タスクを使って local repo に入れたうえで 利用側で mavenLocal() の記載があれば maven repository にデプロイしなくても確認できると思う。

Gradle で BOM を利用する

使い方はおそらく2つ。

  • Gradle Dependency Management Plugin の imports に記載する。
  • Gradle 5.0 以上の場合は implementation platform() する

前者の場合は dependencyMananement の部分に imports で記載すると利用できる。

dependencyManagement {
     imports {
          mavenBom 'io.spring.platform:platform-bom:1.0.1.RELEASE'
     }
}

dependencies {
     compile 'org.springframework.integration:spring-integration-core'
}

Dependency Management Plugin

後者の場合は Gradle 5.0 から Gradle 側で利用方法が提供されているためその書き方もできる。

dependencies {
    // import a BOM
    implementation platform('org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE')

    // define dependencies without versions
    implementation 'com.google.code.gson:gson'
    implementation 'dom4j:dom4j'
}

gradle.org

注意点としては lombokannotationProcessor の利用が必要な場合は annotationProcessorにも BOM の記載が必要になる。

stackoverflow.com

以上

Debian 8 -> 9 にあたり sshd_config に必要だった変更

Debian -- jessie の openssh-server パッケージに関する詳細

openssh 6.7

Debian -- stretch の openssh-server パッケージに関する詳細

openssh 7.4

変更点

Protocol は 7.0 のタイミングでコンパイル時にデフォルトで無効、7.6 のタイミングで完全に削除。 説明文も 7.6 では 1.5 と 1.3 についての記載が削除されている。 debian 9 の openssh は 7.4 のため デフォルトで無効にされており、 Protocol の設定は不要(または設定できない?)になる。

OpenSSH 7.0/7.0p1 (2015-08-11)

OpenSSH is a 100% complete SSH protocol 2.0 implementation and
includes sftp client and server support. OpenSSH also includes
transitional support for the legacy SSH 1.3 and 1.5 protocols
that may be enabled at compile-time.
OpenSSH 7.6/7.6p1 (2017-10-03)

OpenSSH is a 100% complete SSH protocol 2.0 implementation and
includes sftp client and server support.

それに伴い下記設定群が不要(設定できない?)になる

  • RSAAuthentication
  • ServerKeyBits
  • RhostsRSAAuthentication

UsePrivilegeSeparation については設定のデフォルトとしては sandbox になる。 手元の debian サーバの 設定は yes になっているが、 yes と sandbox の違いはよくわからない。

参考URL

差分

今後のバージョン

2018 年まとめ

技術編

  • コードをほぼ書かなかった
  • 小規模 Minecraft サーバを運用した
  • 相変わらず OpenVPN を触っていた
  • Kotlin を少し触った
  • vue.js を少し触った
  • TypeScrypt を少し触った

ゲーム編

2018 年に購入または 2018 年にプレイした形跡があるもの

家財編

  • SHARP の空気清浄機を買った
  • ダイソン Hot & Cool 空気清浄機を買った
  • 無印の超音波式加湿器を買った
  • 象印のスチーム式加湿器を買った
  • I-O-DATA の NAS 16TB を買った
  • CyberPower の UPS を買った
  • パナソニックのレッグリフレを買った

ボードゲーム

  • ゲムマに行って2本のゲームを買った
  • 友人の作ったゲームをやった

漫画編

  • 958 冊買った

映画編

  • グレイテストショウマン
  • カメラを止めるな
  • Search
  • インターステラー
  • Pixel
  • ファイティングマン 怒りの除雪車
  • グレートウォール
  • La La Land
  • セッション

その他

  • 冬桜を見に行った
  • 入籍した