GradleのBOMの機能を使う場合、依存関係のバージョンを古いバージョンで上書きすることはできない

これはGradle 5.0, 5.1, 5.2, 6.1.1で再現しています。 間のバージョンでは再現しないかもしれません。

BOMによって依存関係のバージョンを管理してる際に enforcedPlatformを使ってないのにも関わらず、プロジェクトで個別に依存関係の上書きができる場合と出来ない場合があります。

これはBOMによって挙動が変わっていそうなのですが原因が全く分かりません。 BOMを見比べてもおかしなところは無いように見えます。

条件が分かってないので具体的な例を挙げます。

jackson-bomを使った場合は上書きできる。

dependencies {
  implementation platform('com.fasterxml.jackson:jackson-bom:2.9.7')
  // 上書きできる
  implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.8' 
}

spring-boo-dependenciesを使った場合、なぜか上書き出来ない。 追記: なぜかelasticsearchの依存だけ個別アップグレードできません。なぜ?

dependencies {
  implementation platform("org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE")
  // なぜか上書きできない
  implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client:6.3.2'
}

ワークアラウンドは一つだけあって forceにtrueを設定することで上書きすることが出来ます。

dependencies {
  implementation platform("org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE")
  implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client:6.3.2', { force = true }
}

終わり。 Issueは立てておきました。

github.com

困ったなぁ・・・

追記1

ブログを書いた後に他の依存関係も追加してみたところなぜかelasticsearchの依存だけアップデートできない、という形になっている。なぜだろう・・・

dependencies {
  implementation platform("org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE")
  // We cannot override dependencies!! Why !?!?!?!
  // I am not sure, but I seem that we can not override version of dependency for elasticsearch...
  implementation 'org.elasticsearch.client:elasticsearch-rest-high-level-client:6.3.2' 

  // We can override dependency
  implementation 'com.fasterxml.jackson.core:jackson-core:2.10.2'
  // We can override dependency
  implementation 'io.reactivex.rxjava2:rxjava:2.2.17'
  // We can override dependency
  implementation 'org.hibernate:hibernate-core:5.4.11.Final'
}

追記2

どうやらBOMで指定されているバージョンより古いバージョンを指定することが出来ないようだ。

ref

cloudfoundry/java-buildpack-memory-calculatorをベースに自動でクラスファイルを数えてJVMのメモリ周りのオプションを吐いてくれるツールをGoで再実装した

これは出落ち感があるんですが 既にmaking/memory-calculator-cnbというものがあり UberJarやディレクトリに入ってるクラスファイルを再帰的に、cloudfoundry/java-buildpack-memory-calculatorを使ってJVMオプションを吐いてくれます。

というわけで、今回書いたツールは、making/memory-calculator-cnbに似たツールを再実装した話になります。

ディレクトリもサポートするぞーと思って一通り書いた後に この記事を書こうとして、making/memory-calculator-cnbの実装をちゃんと読んだら ディレクトリもサポートしていることに気が付きました・・・。

なので、実装している機能としてはほぼ一緒のものになります。

追記: cloudfoundry/java-buildpackの実装を参考に 弄ったので、making/memory-calculator-cnbの実装からは微妙に変わりました

追記: 作ったツールのリンクを貼っていませんでした。 貼っておきます。

github.com

はじめに

cloudfoundry/java-buildpack-memory-calculatorでは、loaded-class-countというパラメータがあります。 読み込むクラスファイル数を渡すことで自動でJVMのオプションを吐いてくれます。(他にも必要なパラメータがあるけど)

しかし、元々のcloudfoundry/java-buildpack-memory-calculatorでは、クラスファイルを数えてくれる機能などはありません。 自分でクラスファイル数を数える必要があります。これは面倒です。

そこで、Jarやディレクトリを再帰的に読み込んでクラスファイルを数えて、cloudfoundry/java-buildpack-memory-calculatorを使って JVMオプションを出力してくれたら嬉しいなぁと思って、wreulicke/emcを書きました。

が、先ほども言った通りmaking/memory-calculator-cnbで全く同じものがJavaで実装されています。 変わった点としてはGoで実装したぐらいです。

追記: cloudfoundry/java-buildpackの実装を参考に 弄ったので、making/memory-calculator-cnbの実装からは微妙に変わりました。

インストール方法

インストール方法を書いてなかったので追記しました。 GitHubにReleaseしたバイナリを置いてあるので そこから取ってきて必要があればPATHに入れてください。

# MacOS 
curl -L https://github.com/wreulicke/emc/releases/download/v0.0.1/emc_0.0.1_darwin_amd64 -o /usr/local/bin/emc

# Linux
curl -L https://github.com/wreulicke/emc/releases/download/v0.0.1/emc_0.0.1_linux_amd64 -o /usr/local/bin/emc

# Windows
curl -L https://github.com/wreulicke/emc/releases/download/v0.0.1/emc_0.0.1_windows_amd64.exe -o <path-directory>/emc.exe

使い方

まずはヘルプを見てみましょう。 cloudfoundry/java-buildpack-memory-calculatorとほぼ同じパラメータを持っています。

$ emc --help
usage: emc [<flags>] [<jarOrDirectory>]

Enhanced java memory calculator

Flags:
      --help              Show context-sensitive help (also try --help-long and --help-man).
  -v, --verbose           Verbose
      --total-memory=TOTAL-MEMORY  
                          Total memory. Required if is not limited by cgroup
      --loaded-class-count=LOADED-CLASS-COUNT  
                          Loaded class count
      --thread-count=250  thread count
      --java-options=""   JVM Options
      --head-room=0       Percentage of total memory available which will be left unallocated to cover JVM overhead
      --java-version=11    Java version

Args:
  [<jarOrDirectory>]  jar or directory

ディレクトリを指定する場合は以下のようなイメージです。

$ emc --total-memory 1G <path/to/directory>
-XX:MaxDirectMemorySize=10M -XX:MaxMetaspaceSize=14447K -XX:ReservedCodeCacheSize=240M -Xmx266128K

また、jarを指定することも出来ます。もちろん、UberJarのような Jarの中にJarが入ったようなものでもサポートしています。

$ emc --total-memory 1G <path/to/your.jar>
-XX:MaxDirectMemorySize=10M -XX:MaxMetaspaceSize=14447K -XX:ReservedCodeCacheSize=240M -Xmx266128K

まとめ

完全にmaking/memory-calculator-cnbとほぼ一緒の実装になりましたが Goで実装したので、ある程度バイナリを使い回しやすいのかなと思っています。

終わり。saracen/walker が便利だった。

追記

ちょっとcloudfoundry/java-buildpackの実装を読んでいたら 標準ライブラリを読んでいたり、0.35という割合(多分、jarにあっても読まないファイルがあることを考慮している)を掛けていたりするので そちらに参考にして、標準ライブラリのクラスも加えた上で、0.35を掛けて 読み込むクラス数とするようにした。

微妙にmaking/memory-calculator-cnbから変わった。

実際にカウントしてみて気になったんだけど、java-buildpackの標準ライブラリのファイル数間違えてない?

Go言語でつくるインタプリタを読み終わった

Go言語でつくるインタプリタを読んで 実際に動くインタプリタを書いたので記事として残しておく。

感想

ここ1週間半程度、この本を読んでいた。 気分転換に楽しい本でも読もうと思って Go言語でつくるインタプリタを開いた。

写経しながら読んだが、良い本で、楽しく読める本だった。 また、この本はテスト駆動開発とは書いてないが テスト駆動開発のような形でテストを書いて失敗を確認した後 実装を書いてテストを確認するような流れで書かれている。

またテストが通ったタイミングなどで、「やったね!」とか「完璧だ!」とか「これだけなんだ!」とか 読者の気持ちを盛り上げてくれる言葉が書かれていて 読んでいくごとに実装していくごとに楽しくなっていった。

また、途中で紹介されていたPratt Parsersの記事も読んだ。 この記事はJavaでPratt Parserの説明をしてくれているが、とても分かりやすかった。 Javaなので、読みやすいし。

ちなみに、自分は、構文解析と簡単なインタプリタは書いたことはあるが、専門家ではないが 本で実装する言語に最低限必要なことは書かれているし、テストもあって試しやすい。 goで書いていたこともあって、テストの実行のサイクルが早かったのも良い体験だった。

字句解析や構文解析など、インタプリタを書いたことがない人にはオススメの本だ。 興味を持った人は読んでみてほしい。

ちなみに、マクロには興味がなかったので飛ばした。

拡張してみた部分

本には実装されていない機能として以下のような物を実装してみた。

  • runeを使ったlexer
  • tokenだけ小数点数のサポート(実際に実行しようとするとエラーで落ちる)
  • エスケープ文字(タブとか改行とか)
  • パイプラインオペレータ
  • 配列やハッシュ形式のDestructuring
    • let文におけるDestructuring
    • 関数リテラルにおける引数のDestructuring

実はruneや小数点数やエスケープ文字は昔書いたlexerを本の実装に合わせて移植したものである。 なので、今回実際に本に載ってないもので実装したのはパイプラインオペレータとDestructuringがメインになる。

パイプラインオペレータによって、以下のようなコードが動く。 (REPLで実行した例だ)

>> 2 + 2 | fn(x) { x }
4

また、Destructuringによって以下のようなコードも動く。 これは楽しい。

>> let [x] = [2]; x
2

>> let {x} = {"x": "2"}; x
2

>> 2 | fn(x) { x }
2

>> [1, 2] | fn([x, y]) { x + y }
3

>> {"x": 1, "y": 2} | fn({x, y}) { x + y }
3

パイプラインオペレータもDestructuringも比較的簡単に実装が出来たので楽しかった。 実際に実装したコードは以下のリポジトリに置いてある。

github.com

ちなみに、とりあえず動く形にしただけなので エラーハンドリングはだいぶ雑だ。

終わりに

Go言語でつくるインタプリタを読んで、本に書かれているのと同じくGo言語を使って、monkey言語を実装した。 パイプラインオペレータやDestructuringなども本には書かれていないが実装してみた。 基本的に読んでいて楽しいし、写経するだけでも楽しい本だった。

Go言語でつくるインタプリタは良い本だったので興味があったら読んでほしい。

終わり。