おおくまねこ

職業プログラマーです。興味のある話題を書いています。

「良いコード/悪いコードで学ぶ設計入門―保守しやすい 成長し続けるコードの書き方」を読みました

はじめに

技術書を読みました。 読んだのは以下の本、「良いコード/悪いコードで学ぶ設計入門―保守しやすい 成長し続けるコードの書き方」です。

設計について有名なミノ駆動さんが書かれた本ということで発売前から注目度の高かった本なので、 ご存知の方も多いと思います。 逆張り思考の私としては「読んでなるものか」と思っていたのに、誘惑には勝てず購入をしてしまい、 読んだ後には充実感を得るほどの満足できる内容の本だったため、ブログに記載してみようと思いました。

該当書籍を読んでみて

おすすめしたい部分

実際に読んで感じた、おすすめできる部分として以下の部分を感じました。 - 構成が構造的、表現も理解しやすい内容の文章で読みやすい - 悪い設計の例がかなり具体的(あるあるすぎて共感する) - 悪い例の後に、良い例を説明している。悪い例の課題への対処方法が書かれている。 - 経験があるエンジニアの持っている「設定をするときの当たり前」をしっかり言語化されている

もう少し

まず、かなり「読みやすかった」というのが第一印象です。 構成がしっかりされていて、課題の説明から入り、解決方法について、解説という流れが全体にあるので、 納得しながら読み進められました印象です。 表現も理解しやすいもので説明されている印象だったため、解釈がとてもわかりやすかった印象です。

内容で説明されているリファクタリングテクニックもかなり具体的かつ実用的なものが多いと感じました。 悪い設計・実装例について、例文がソースコードとして載っていますし、 問題点だけでなく、しっかりその対応方法についても説明されているのがとても好印象を受けました。 どういう風に当てはめれば良いか、という点についても前述した課題の説明からされているので、想像しやすいと思います。

この手の本や資料は「課題だけ」や「テクニックだけ」となるものも少なくない印象ですが、両方説明されているのが良いと感じました。 「理想像をイメージする大切さ」について、著者が取り組んできた空手の経験から説明もされていましたので、 そういった部分を意識し、意図した内容になっているのかなと想像しています。

発売記念勉強会1で話されていた時、「プログラムの入門を終え、設計の専門書に挑む前に読むエントリー本のイメージ」という旨の発言をされていたと記憶しているのですが、 まさにその表現の通りの内容になっていると感じました。

一度設計について学んだ人でも、再確認や復習として、とても参考になるのではないかと思います。

さらに勉強を進めたい人用に参考書籍の紹介や取り組み方も最終章に記載されているので、どこまでもフォローが行き届いているという充実ぶりでした。

M1 Macと docker compose の付き合い方について考えた

はじめに

背景

Mac Book Air 2020 年モデル、いわゆるM1 Macを買いました。 今まで使っていた Dockerを動かすとき、docker composeの使い方が変わったので、その内容について記載します。

まえがき

CPUが Apple Silicon と呼ばれるもので、ARMプロセッサーのCPUになります。 以前までのIntell製のCPUと違う点がよく挙げられるのですが、 ソフトウェア開発者視点で遭遇するものの一つにDockerがあります。

今までIntel製で動いていたDockerファイルが動かない。と言われたり、実際になったりすることがあります。 というか、なりました。

その時の Docker、docker compose の使い方についてをまとめようと思います。

環境

今回試した環境は以下 - OS: macOS Monterey - CPU: AppleM1 - メモリ: 16GB

方法

具体的な対応方法について、知っている範囲で説明を記載していこうかと思います。

ARM CPU 対応版の Docker ファイルを利用する

既存のDockerファイルがあり、コミュニティの方でDockerファイルを管理されている場合、 ARM CPU 対応されたイメージがある場合があります。

cassandra を例にすると、Intel CPU で動く cassandra、 ARM 対応した arm64v8/cassandra があります。 そういった対応版を使うとよさそうです。

その他:アーキテクチャを指定する

Docker の起動時、プラットフォームを指定する方法です。 マルチ CPU アーキテクチャのサポートを活用 — Docker-docs-ja 19.03 ドキュメント

1年ほど前(2021年)の時点では、mysql などはM1 Mac で起動すると起動が失敗するケースがあったようです。 (いま手元で試したら mysql は起動したので、Docker側で対応されている気がする)

その際は docker で起動した時にアーキテクチャ指定を行うことで起動していました。 起動時のオプションに--platform linux/x86_64を指定します。

docker compose について

Intel CPU、arm CPU が混在する環境、チームではどうすると良いかと考えた時について。 個人的な解決しては「docker composeファイルの差分ファイルを作って、読み込むようにする」という結論に落ち着きました。

docker-compose.yaml は複数指定すると、差分を上書きして実行してくれます。

ファイル間、プロジェクト間での Compose 設定の共有 — Docker-docs-ja 19.03 ドキュメント

これを利用して、開発者ごとに差分を読み込むようにします。

共通の docker compose ファイル。docker-compose.yamlとする。

version: '3'
services:
  sandbox-service:
    build:
      context: .
      dockerfile: ./Dockerfile
    tty: true
    volumes:
      - ../:/app
    working_dir: /app
    ports:
      - 8080:8080
  cassandra:
    image: cassandra:4.0
    tty: true
    ports:
      - 9042:9042

差分の docker compose ファイル。m1mac.yamlとする。

version: '3'
services:
  cassandra:
    image: arm64v8/cassandra:4.0

それぞれの環境ごとでコマンド実行時に読み込みを行います。

docker compose -f docker-compose.yaml up -d
  • ARM CPU ユーザー
docker compose -f docker-compose.yaml -f m1mac.yaml up -d

アーキテクチャ指定で起動する場合でも、docker-compose ファイル、環境差分ファイルにも起動オプションに上記を指定すれば問題ないのかなと思います。

終わりに

無事にM1Mac でも Docker、docker composeが使える目処がたちました。 まだ触れ出したばかりなので、色々試していきたいと思います。

SpringBoot/Gradleのlogback脆弱性更新したときの方法

はじめに

log4j2 の脆弱性 が2021年12月10日に報告され、

任意のプログラムを実行できる危険性があるため、早急に対応することが必要という事で話題になりました。

logback にも「同じ脆弱性の懸念がある」という報告が12月14日におこなわれました。

https://jira.qos.ch/plugins/servlet/mobile#issue/LOGBACK-1591

これらの脆弱性の対応方法はライブラリのバージョンアップすることになります。

log4j2, logback の更新を自分のレポジトリーで行ったので、実績として残しています。

脆弱性バージョン更新の対応

log4j 2 の脆弱性対応について

私の関係しているものでは log4j2(log4j-core) をログ出力の実装ライブラリとしてつかっているのはなかったのですが、

slf4j は使っていたので、log4j2-api は依存として含まれていました。

脆弱性の原因とされていた JNDI lookup 機能は log4j-core で使われているので、関係ないのですが、

影響度が大きさと、あげない理由もなかったので対応しておこうと思いました。

その時に参考にさせていただいたブログは以下になります。

自分は SpringBoot/Gradle/io.spring.dependency-management plugin の構成だったので、

上で参考にさせていただいたブログのとおり、以下の記述を build.gradle に設定することで対策できました。

ext['log4j2.version'] = '2.15.0'

※ブログ記載現時点で lookup 機能自体が削除された 2.16.0 がリリースされているので、そちらを使うのが良いと思います。

補足

この方法が良いと思ったのは、関連する log4j 2 のライブラリバージョンをまとめてバージョンアップできるためです

dependencies で個別のライブラリのバージョンを更新しても対象のライブラリの更新は可能ですが、

他にも log4j 2 関連の依存ライブラリが含まれていて、更新しておけばよかったとか、

log4j 2 のバージョンが異なって食い合わせが良くないとが発生するのが気になるところが懸念としてありました。

※バージョン違いによる懸念は参考にさせてもらったブログにも書かれていましたね。IFやシグネチャの変更などが理由。

logback脆弱性対応

12月14日時点ですでに対応版の 1.2.8 がリリースされているので対応しました。

log4j 2 と同様にgradleextlogback のバージョンを指定することでバージョン更新可能です。

  • build.gradle
ext['logback.version'] = '1.2.8'

アップデートの確認。依存する logback1.2.8 に更新されているのが確認できる

> .\gradlew dependencies |findstr logback
|    |    |    +--- ch.qos.logback:logback-classic:1.2.7 -> 1.2.8
|    |    |    |    +--- ch.qos.logback:logback-core:1.2.8
|    |    |    +--- ch.qos.logback:logback-classic:1.2.7 -> 1.2.8
|    |    |    |    +--- ch.qos.logback:logback-core:1.2.8
|    |    |    +--- ch.qos.logback:logback-classic:1.2.7 -> 1.2.8
|    |    |    |    +--- ch.qos.logback:logback-core:1.2.8
|    |    |    +--- ch.qos.logback:logback-classic:1.2.7 -> 1.2.8
|    |    |    |    +--- ch.qos.logback:logback-core:1.2.8
|    |    |    +--- ch.qos.logback:logback-classic:1.2.7 -> 1.2.8
|    |    |    |    +--- ch.qos.logback:logback-core:1.2.8

追伸

build.gradledependencies にライブラリを直接している場合は注意した方がよさそうです。

そちらにバージョン指定していた場合、そちらが優先されるため。

dependencies {
    // log
    implementation "ch.qos.logback:logback-core:1.2.7"
}
> .\gradlew dependencies |findstr logback
|    |    |    +--- ch.qos.logback:logback-classic:1.2.7 -> 1.2.8
|    |    |    |    +--- ch.qos.logback:logback-core:1.2.8 -> 1.2.7
+--- ch.qos.logback:logback-core:1.2.7
+--- ch.qos.logback:logback-core:1.2.7 (n)
|    |    |    +--- ch.qos.logback:logback-classic:1.2.7 -> 1.2.8
|    |    |    |    +--- ch.qos.logback:logback-core:1.2.8 -> 1.2.7
+--- ch.qos.logback:logback-core:1.2.7
|    |    |    +--- ch.qos.logback:logback-classic:1.2.7 -> 1.2.8
|    |    |    |    +--- ch.qos.logback:logback-core:1.2.8 -> 1.2.7
+--- ch.qos.logback:logback-core:1.2.7
|    |    |    +--- ch.qos.logback:logback-classic:1.2.7 -> 1.2.8
|    |    |    |    +--- ch.qos.logback:logback-core:1.2.8 -> 1.2.7
+--- ch.qos.logback:logback-core:1.2.7
|    |    |    +--- ch.qos.logback:logback-classic:1.2.7 -> 1.2.8
|    |    |    |    +--- ch.qos.logback:logback-core:1.2.8 -> 1.2.7
+--- ch.qos.logback:logback-core:1.2.7

最後に

迅速な対応をいただいたライブラリメンテナーの皆様、 情報共有をいただけた皆様にまことに感謝いたします。

Java /Gradle shadow プラグインの力で fat Jar を簡単に作る

はじめに

動機

今はあまりなくなってしまいましたが、Java のプログラムを fat Jar として作って頒布する機会があったので、その時に試した方法について記載します。

Java は長期的に動くサーバーサイドの開発などに使うことが多いと思うので、そういうケースは少ないと思いますが、

Java の資産を使ったモジュールを作ったり、古いソフトウェアのメンテナンスなんかでそういうユースケースがあるという認識です。

 

Gradle の shadow plugin*1 を使えば簡単に fat Jar を作れました。

環境

今回、私の実行するために利用した環境は以下になります

fat Jar について

fat Jar とは依存関係含む、すべての class を含んだ jar ファイルのことです。

 

plugins {
id 'java'
}

group 'com.github.keyno63'
version '1.0-SNAPSHOT'

repositories {
mavenCentral()
}

dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
}


// jar 実行の manifest の設定
jar {
manifest {
attributes "Main-Class": "com.github.keyno63.app.Main"
}
}

 

例えば、Jackson を使ってコマンドラインから渡した Json 文字列を分解するような、

以下のようなプログラムがあったとします。

package com.github.keyno63.app;

import ...;

public class Main {
public static void main(String[] args) throws JsonProcessingException {
final List<String> argsList = Arrays.asList(args);
if (argsList.size() > 0) {
ObjectMapper mapper =
new ObjectMapper();
final JsonData json = mapper.readValue(argsList.get(0), JsonData.class);
System.out.println(json);
}
}


public static class JsonData {
private final String value;

@JsonCreator
public JsonData(@JsonProperty("json_key") String value) {
this.value = value;
}

@Override
public String toString() {
return String.format("""
{
"json_key": "%s"
}
""", value);
}
}
}

単純にビルドして実行すると以下のように失敗するかと思います。

> .\gradlew clean build

BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 executed
> java -classpath ".\build\libs\gradle-shadow-1.0-SNAPSHOT.jar" com.github.keyno63.app.Main '{\"json_key\": \"json_value\"}'

エラー: メイン・クラスcom.github.keyno63.app.Mainを初期化できません
原因: java.lang.NoClassDefFoundError: com/fasterxml/jackson/core/JsonProcessingException

依存関係の com.fasterxml.jacksonが生成した jar に含まれていないので、実行時に依存関係が解決されずに失敗します。

 

 実行するためには依存関係のある jar への classpath を通すと実行することもできます。

> java -classpath "<jackson の jar への path>;.\build\libs\gradle-shadow-1.0-SNAPSHOT.jar" com.github.keyno63.app.Main '{\"json_key\": \"json_value\"}'
{
"json_key": "json_value"
}

 

それでも動くのですが、頒布する場合とかに利用してもらう人に依存関係の jar ファイルを用意してもらうのもハードルが高いので、fat Jar を作ることにしました。

 

fat Jar にすれば、依存関係をすべて含むその jar のみで完結して実行することができます。

> java -jar .\build\libs\gradle-shadow-1.0-SNAPSHOT-all.jar '{\"json_key\":\"json_value\"}'
{
"json_key": "json_value"
}

 

fat Jar を作る選択肢

fat Jar を作る他の選択肢としてあがるのは以下になるかと思います。

  • build.gradle 設定を変更して作る*2
  • gradle-fatjar-pluginを使う*3
  • gradle shadow plugin を使う

 

build.gradle 設定を変更する方法は複雑かつややこしいように感じたので、できれば自動化に近い仕組みを使いたかったので選択肢から外れました。

gradle-fatjar は 2015 年以降更新されていないので、選択肢から外れました。

 

以上の理由から shadow プラグインを使うようしました。

Gradlew にshadow プラグインを使う

すること

対応することは以下の2点です。

  • plugin に shadow を追加する
  • gradle shadow コマンドでfatJar を作る

build.gradle の編集

build.gradle にすることは plugins に shadows を追加するだけです。

plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '7.1.0' // 追加
}

jar をそのまま実行できるように manifest の設定を追加しておくのもお勧めです。

// jar 実行の manifest の設定
jar {
manifest {
attributes "Main-Class": "com.github.keyno63.app.Main"
}
}

 

fat Jar 生成

あとはターミナル、およびIDEのコマンドから gradle shadowJar相当を実行します。

デフォルト設定のままであれば .\build\libs配下に実行用の jar ができています。今回であれば gradle-shadow-1.0-SNAPSHOT-all.jar というのができています。

> .\gradlew clean shadowJar
> dir .\build\libs\gradle-shadow-1.0-SNAPSHOT-all.jar

jar の中身をみると、依存関係のパッケージのクラスも含まれているのがわかります。

> jar tf .\build\libs\gradle-shadow-1.0-SNAPSHOT-all.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/github/
com/github/keyno63/
com/github/keyno63/app/
com/github/keyno63/app/Main$JsonData.class
com/github/keyno63/app/Main.class
META-INF/LICENSE
META-INF/maven/
META-INF/maven/com.fasterxml.jackson.core/
:

動作確認

生成した jar を指定して、実行可能なのが確認できます。

> java -jar .\build\libs\gradle-shadow-1.0-SNAPSHOT-all.jar '{\"json_key\": \"json_value\"}'
{
"json_key": "json_value"
}

以上で無事やりたいことが達成できました。

 

最後に

かなり簡単に目的を達成できる方法だと感じました。

追加の設定も特に不要なのがありがたいですね。

scala-steward を個人レポジトリの GitHub Actions で動作させられた記録

はじめに

scala-stewardGitHub Actions から実行し、

自分のレポジトリに対してライブラリ更新ができるようにできたので、

その時の内容を記載します。

 

設定方法がよくわからなかったり、設定項目が何が必要かがわかりにくかったりしたので

それについても残しておきたかった所存です。

 

作った scala-steward を使うための GitHub Actions の設定レポジトリは以下です

github.com

scala-steward とは

Scala プロジェクトが依存するライブラリがバージョンアップリリースが存在していると、更新するためのPRを自動生成してくれるシステムです。

対抗というか、類似のツールでは Renovatedependabot などがあります。

 

モチベーション

なぜ scala-steward か

他にもコンペのツールがある中で scala-steward を使ったかというと、大きくは以下の2点です。

  • 他言語での開発している自前レポジトリでは Renovate, dependabot を使っているので scala-steward をつかってみたかった
  • 多くの有名Scalaプロジェクトで使われているので興味があった

 

以下の記事によると、Renovate でも結構いけるらしいのですが、

tarao.hatenablog.com

やっぱり scala-steward を使ってみたかったので自分の環境下で動かせるようにしました。

GitHub Actions で動かす理由

実はわざわざ自分で実行環境を用意しないでも scala-steward を使う方法があります*1

 scala-steward の公式が動作する環境を用意してくれていて、

対象レポジトリ一覧に自分の管理するScalaプロジェクトのリポジトリを追加すれば動かすことが可能です

repos/repos-github.md at main · scala-steward-org/repos · GitHub


この方法のネックとしては「更新リクエストを送ってマージしてもらう必要がある」、「自分の作業用レポジトリを追加してもらうのが恥ずかしい」というのがあります。

なので自分で気兼ねなく使える環境が欲しかったため、GitHub Actions での環境を用意しました。

GitHub Actions を使う場合は以下のテンプレートを使わせてもらいます。

github.com

 

設定方法

前提条件

今回は以下の環境で設定することを前提になっています

  • scala-steward を実行する専用のレポジトリから動作させます
  • GitHub App からコミット、PR生成してもらうようにします

設定に必要なこと

設定に必要な作業は以下

  • GitHub App を生成します
  • GitHub App の秘密鍵を生成します
  • scala-steward 実行する専用のレポジトリを GitHub に作成します
  • レポジトリの設定の secrets に以下の登録します
  • GitHub Actions の workflow を作成します
  • scala-steward の実行対象のレポジトリを決定する設定ファイルの作成
  • GitHub Actions のジョブを有効化します

設定手順

GitHub App を生成

GitHub App の生成するページから「New GitHub App」のボタンをクリックして、

今回使うようの App を作成します。

設定した項目は以下にしてました

  • GitHub App name
    • 好きな名前でいいです。今回は「keyno63-scala-steward」とかにしました。
  • Homepage URL
    • 自分の GitHub アカウントのURLを入れてました
  • Webhook
    • デフォルトでは有効でしたが、今回いらないと思ったのでチェックボックスを外して無効化しました
  • Repository permissions
    • 「Contents」、「Pull Request」を「Read&Write」にします
  • そのほかはデフォルトです。

以上の設定をして、「Create GitHub App」をクリックすると、Appが生成されます。

以下のようになっていれば問題ありません*2

f:id:keyno63:20211128040249p:plain

GitHub App の秘密鍵を生成

App 作成したら、作った App の「Edit」をクリックするか、

https://github.com/settings/apps/<作成したアプリケーション> にアクセスして、

App の編集画面に遷移します。

 

画面下のほうまでスクロールして、「Private Keys」という項目まで移動します。

「Generate a private key」をクリックして秘密鍵を生成します。

秘密鍵を生成出来たら、.pem ファイルが自動でダウンロードされます。

後で使うのでおいておいてください。

 

scala-steward 実行用レポジトリを生成

GitHub から新規レポジトリを作成します。

https://github.com/new

 

ここで作るのは空レポジトリで問題ありません。

必要なファイルは .github/workflows/***.yamlだけです。これはあとで作るので今は何もなしでもよいです。

 

secret の設定

レポジトリの設定から secretを設定します。

先ほど作成したレポジトリの「Settings」、左メニューの「Secrets」をクリックして遷移します。

「New repository secret」ボタンをクリックして Secret生成画面に遷移します。

f:id:keyno63:20211128042815p:plain

もしくはhttps://github.com/<GitHubアカウント名>/<作成したレポジトリ名>/settings/secrets/actions/newにアクセスしてもらっても大丈夫です。

 

Nameに後で使う設定名を、Valueには先ほど設定した GitHub App の秘密鍵の中身をコピーして貼り付けてください

自分は「SS_PRIVATE_KEY」という設定名で作成したので以下のようになりました。

同じように Secretが作成されていれば問題ありません。

f:id:keyno63:20211128043548p:plain

GitHub Workflows の作成

GitHub Workflows を作成します。GitHub Actions 用の yaml ファイルを作成します。

以下のように作成して貰えれば大丈夫です*3

name: CI
on:
schedule:
- cron: '0 0 * * 0'
pull_request:
push:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Generate token
id: generate-token
uses: tibdex/github-app-token@v1
with:
app_id: <作成した app id>
private_key: ${{ secrets.SS_PRIVATE_KEY }}
- name: Launch Scala Steward
uses: scala-steward-org/scala-steward-action@v2
with:
github-token: ${{ steps.generate-token.outputs.token }}
author-name: "<作成した App >[bot]"
author-email: "<作成した App user id>+<作成した App >[bot]@users.noreply.github.com"
repos-file: 'repos.md'

実例は以下

https://github.com/keyno63/scala-steward-mine/blob/2e31af53b972ef50f2393616b855a4e1dddf66ba/.github/workflows/ci.yaml

 

補足:app id について

作成した GitHub App の App ID のことです。

GitHub App の編集画面から確認できます。

f:id:keyno63:20211128044649p:plain

補足:author-email の取得

GitHub App のユーザー情報取得するAPIから、user id を取得できれば、App用の email が取得できます。

https://api.github.com/users/<作成した App 名>%5Bbot%5D

レスポンスの jsonidの値がわかれば、今回作成したApp名はわかるので、email が作れるというわけです。

対象レポジトリの設定ファイルを作成

workflows の yaml で指定した repos-fileのファイルを作成し、実行対象のレポジトリを指定します。

md ファイル形式で列挙して貰えればいいです。

https://github.com/keyno63/scala-steward-mine/blob/2e31af53b972ef50f2393616b855a4e1dddf66ba/repos.md

GitHub Actions を有効化してPR作成

GitHub Actionsからジョブが実行されて、ライブラリの更新があればPRが自動生成されます。

 

実際に作成されたPR

https://github.com/keyno63/heroku-scala-app/pull/5

 

作成した App で修正コミットがされていること、PRが生成されていることがわかります。

f:id:keyno63:20211128050140p:plain

ここまでできていれば完成です。

 

うまくいかなかったこととか、補足とか

補足情報的な項目です。

読まなくても設定は可能なので読み飛ばしてもらっても問題ありません。

うまくいかなかったこと

scala-steward-org/scala-steward-actionの設定に App として動作させるための項目に

github-app-idgithub-app-keyを設定するのがあるのですが、

repos/repos-github.md at main · scala-steward-org/repos · GitHub

これを設定した場合に repos-file による、特定のレポジトリのみに対する更新ができませんでした。

まだ中身をちゃんと確認していないので原因は不明

 

補足

GithubApp を作った理由

GitHub App から自分以外が更新した・PRの生成であることがわかるように作成しています。

「自分の活動履歴が実際のものと異なる」、「自分の作業と区別したい」という理由があったのでこの方法を採用しました。

 

「自分のコミット・PRでも構わない」という人は作成しなくても問題ないかもしれません。

その場合は workflow 中の token 生成は不要です。

代わりにgithub-tokenには自分のアカウントの token を sercret に設定し、

author-nameauthor-emailは自分の GitHub アカウントに紐づいたものを使用して

使ってください。

 

GitHub Action 中で token の生成について

GitHub App からの PR にするために使用します。bot からのPRであることを識別してもらうため、workflow の中で token を生成してもらっています。

uses: tibdex/github-app-token@v1の部分があたります。

 

これをせず、自分の token を secrets に登録、それを利用するとすると、

PRは作成できるのですが、作成者が自分になるという悲しい結果になります。

 

失敗してしまった例

https://github.com/keyno63/sbt-heroku/pull/4

f:id:keyno63:20211128052432p:plain

コミットは App になっていそうだが、PR作成者は自分のアカウントになる。

author-name, author-email について

author-nameauthor-emailはコミットするときのユーザー情報です。

これも自分の実績と分けたかったので、設定しています。

 

参考情報

今回の作業にあたり、以下の情報を参考にさせていただきました。

最後に

ずっと興味のあった scala-steward が自分のレポジトリに適応できるようになったので感無量です。とてもうれしかった。

*1:GitHub Enterprise や private repository を使っている場合はダメだったかもしれません

*2:このAppはアイコン画像を設定している状態です

*3:項目の説明は後述

Scala/sbt で設定した依存関係がコンフリクトしてしまったら

概要

Scala/sbt を使っていて、依存関係で追加したライブラリ間で依存関係が衝突してしまったときにどうすればいいかという内容です。

Java/gradle だと自動的に新しいバージョンを使うようしてくれていたのですが、どうも sbt ではそうはいかないらしいということがわかりました。

その時にどうすれば回避できるのかについて記載しています。

 

事象

自分は scalikejdbcakka-streamを依存関係に追加した時に、scala-parser-combinatorsのコンフリクトで発生しました。

lazy val akkaPj = project
.in(file("akkaPj"))
.settings(
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-stream" % "2.6.17",
"org.scalikejdbc" %% "scalikejdbc" % "4.0.+",
)
)

 

sbt の読み込みを行うと以下のような結果になります。

[error] java.lang.RuntimeException: found version conflict(s) in library dependencies; some are suspected to be binary incompatible:
[error]
[error]         * org.scala-lang.modules:scala-parser-combinators_2.13:2.1.0 (early-semver) is selected over 1.1.2
[error]             +- org.scalikejdbc:scalikejdbc-core_2.13:4.0.0        (depends on 2.1.0)
[error]             +- com.typesafe:ssl-config-core_2.13:0.4.2            (depends on 1.1.2)

ssl-config-coreakka-streamの子依存)が scala-parser-combinators-1.1.2に依存していて、

scalikejdbcscala-parser-combinators-2.1.0に依存しているため、

コンフリクトが発生している状況になります。

 

対応方法

dependencyOverridesを sbt で設定することで対応できました。

ここで指定したライブラリをそのバージョンで override することができました。

lazy val akkaPj = project
.in(file("akkaPj"))
.settings(
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-stream" % "2.6.17",
"org.scalikejdbc" %% "scalikejdbc" % "4.0.+",
),
dependencyOverrides += "org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.0"
)

今回ではscala-parser-combinators-2.1.0を優先する設定にしています。

 

以下の sbt の公式マニュアルにも説明されていました。

www.scala-sbt.org

 

参考

以下も参考にしました。

ビルドツールのセマンティクスについての説明や検証について書かれて大変勉強になりました。


sbt1.4系からsbt1.5系に移行したいけどうまくいかなかったあなたへ

はじめに

Scala のビルドツール sbt のバージョンアップをするときに途方にくれていたのですが、

実は結構簡単にできる方法があると教えてもらったので、その時のことを記録しています。

 

特に 1.4 系から 1.5 系に移行するときに大幅な変更があり、その差分変更にてこずった話になります。

同じように 1.4 系から、もしくはそれ以前のバージョンから更新したい場合に参考になるのではと思っています。

 

sbt バージョンアップ

sbt はバージョンアップに伴って大幅な機能追加・機能変更がされています。

特に sbt 1.5 系から Scala3 のビルド機能が追加されるなど注目の変更もありました。

 

私もそれにつられて sbt 1.5 を使うようにしているのですが、

古いプロジェクトで sbt 1.4 をつかっていて、更新したときに build.sbt の変更が必要になりました。

具体的には build.sbt で使う in というメソッドが非推奨になっています。

どういう実装のものかというと、以下のような記述のケースです。

version in webpack := "4.43.0",
version in startWebpackDevServer := "3.11.0",
webpackConfigFile in fastOptJS := Some(baseDirectory.value / "webpack" / "webpack-fastopt.config.js"),
webpackConfigFile in fullOptJS := Some(baseDirectory.value / "webpack" / "webpack-opt.config.js"),
webpackConfigFile in Test := Some(baseDirectory.value / "webpack" / "webpack-core.config.js")

 

この記法の代わりにスラッシュ構文という / を使う必要があるそうです。

設定箇所が複数あると、ひとつずつ変えていくのも大変ですし、どうしようかなと悩んでいたらTwitterで「scalafix を使ってマイグレーションができる」という方法を教えてもらえました。

 

scalafix は設定方法に従って Scala や sbt のファイルを書き換えてくれる機能です。

sbt プラグインとして使うことが多い印象でしたが、コマンドラインとして実行する方法もあるようです。

 

sbt 1.4 から sbt 1.5 へ、scalafix を使って移行する方法については以下のブログが詳細に書かれていました。

eed3si9n.com

 

以上のブログで十分かもしれませんが、もう少し補足させてもらいます。

やることとしては以下

  • (入っていない場合は)cs コマンドを入れる*1
  • scalafix をインストールする
  • scalafix をコマンドへ実行パスを通す
  • 前述したブログの内容のコマンドを実行します。

cs コマンドのインストール方法については以下を参考にしてください*2

get-coursier.io

 

cs コマンドを使って、scalafix をインストールします。

cs コマンドからインストールすると、Coursier のホームディレクトリ以下にインストールされてしまうので、コマンドラインから実行できるように実行PATHを設定します*3

 

コマンドが通るようになったら、以下のコマンドを、sbt を更新したいプロジェクトのなかで実行します。

>scalafix --rules=https://gist.githubusercontent.com/eed3si9n/57e83f5330592d968ce49f0d5030d4d5/raw/7f576f16a90e432baa49911c9a66204c354947bb/Sbt0_13BuildSyntax.scala *.sbt project/*.scala

 

以上のコマンドを実行した結果、以下のように書き換えが行われました。

sbt 1.5 を起動すると、エラーや Warning が出力されなくなり、無事に移行することができました。

(webpack / version) := "4.43.0",
(startWebpackDevServer / version) := "3.11.0",
(fastOptJS / webpackConfigFile) := Some(baseDirectory.value / "webpack" / "webpack-fastopt.config.js"),
(fullOptJS / webpackConfigFile) := Some(baseDirectory.value / "webpack" / "webpack-opt.config.js"),

 

さいごに

Scala は大幅な変更がたびたび入る言語ですが、移行の大変さを軽減するためのツールや仕組みが充実していると感じています。

私の周り*4には「Scalaはメンテが大変だ」「Scala3もリリースされて移行を考えると採用しづらい」という意見を聞くこともあります。

確かに自分もそういう風に感じることがありますが、Scalaでの開発大変は他では得難いことも多くあると感じているので、そこであきらめる人やチームを減ってほしいなと思っています。

こういうツールがある、こういう対応方法が用意されている、ということを多くの人が知ってほしいと思っています。

参考

*1:Coursier というツールの略です。scalafix をインストールするために必要です

*2:Windows/Mac/Linux すべてに対応しています

*3:実行PATHの設定方法は各OSでの設定方法を参考にしてください。

*4:主観かもしれません。職場の別チームや勉強会でそういう意見を聞くなという印象をもっています。