kagamihogeの日記

kagamihogeの日記です。

JEP 223: New Version-String Schemeをテキトーに訳した

http://openjdk.java.net/jeps/223 をテキトーに訳した。

JEP 223: New Version-String Scheme




Summary

JDKのバージョン文字列規則をメジャー・マイナー・セキュリティアップデートリリースを区別しやすいように修正します。

Goals

  • 人間が理解しやすく、また、プログラムがパースしやすいようにします。
  • 現在の業界標準、具体的にはSemantic Versioningに合わせます。
  • 既存のパッケージングシステムとプラットフォーム依存のメカニズムであるRPM, dpkg, IPS, Java Network Launching Protocol (JNLP)などが使用可能なものにします。
  • バージョン文字列の一つの要素に二種類の情報を混ぜ込む現行方式を削除します。すなわち、マイナーリリース番号とセキュリティレベルでは解読が難しく、また、多くのバージョン番号をスキップする結果となっています。
  • バージョン文字列のパース・検証・比較を行うシンプルなAPIを提供します。

Non-Goals

  • このJEPのターゲットリリース以前で使用されているバージョン文字列のフォーマットも変更されます。

Motivation

JDK 7 Update 55とJDK 7 Update 60のうち、最も新しいセキュリティフィックスが含まれるのはどちらでしょうか?

Update 55よりもJDK 7 Update 60の方が5回多くリリースをしているように見えるので、60の方が最も新しいセキュリティを含んでいる……これは正しいでしょうか?

答えは、残念なことに、不正解です。この二つのリリースは全く同一のセキュリティフィックスを含んでいます。このカラクリを理解するには、まずJDKアップデートリリースのためのcurrent numbering schemeを理解する必要があります。セキュリティフィックスの変更を含むマイナーリリースは20の倍数になります。以前のマイナーリリースに基づくセキュリティリリースは5ずつ増加する奇数か、更新する番号を奇数に維持する必要があれば6ずつにします。以前のリリースと比較してセキュリティ修正があったかどうかを判別するには、突き詰めていくと、リリースノートかソースコードを参照する必要があります。

リリース名である"JDK 7 Update 60", "1.7.0_60", "JDK 7u60"の違いは何でしょうか?

これらは同一リリースに対しての異なる名前なだけです。このような差異はリリースが同一であることの検証や識別を難しくします。シーケンスを何らかのデリミタでトークンにパースする単純な比較では不十分で、極めて洗練されたアルゴリズムが必要とされます。小文字の'u'の使用は業界標準では無く、言語依存です(not language-neutral.)。

バージョンスキーマが直感的でシンプルだった時代は遠い昔のことになっています。

Description

Version numbers

バージョン番号(version number)とは、空でない非負の整数で、ゼロから始まらず、ピリオド(U+002E)で区切られます。つまり、正規表現[1-9][0-9]*(\.(0|[1-9][0-9]*))*にマッチします。任意の長さが可能ですが、以下に示すように、最初の三つの要素は特定の意味を持ちます。

$MAJOR.$MINOR.$SECURITY
  • $MAJOR --- メジャーバージョン番号のことで、Java SE Platform Specificationの新エディションなどに重大な変更を伴う新機能を含むメジャーリリース時に、カウントアップします。たとえば、JSR 337におけるJava SE 8などです。メジャーリリースでは機能によっては削除されるものがあり、そうした機能はメジャーリリース以前に前もって告知がなされ、正当な理由があれば互換性の無い変更も実施されます*1JDK 8の$MAJORバージョン番号は8であり、JDK 9の$MAJORバージョン番号は9になります。
  • $MINOR --- マイナーバージョン番号のことで、これに含まれるものとしては、互換性のあるバグフィックス(compatible bug fixes)、関連Platform SpecificationのMaintenance Releaseが管理する標準APIの改訂、新しいJDK固有APIのような仕様の範囲外の実装機能、追加されるサービスプロバイダ、新しいガベージコレクター、新しいハードウェアアーキテクチャへの移植、があり、マイナーアップデートリリース時にカウントアップします。$MINOR$MAJORがカウントアップするときにゼロにリセットされます。
  • $SECURITY --- セキュリティレベル(The security level)は、セキュリティ的に改善が必要な内容から構成される、クリティカルフィックスを含むセキュリティアップデートリリースのことです。また、その際にカウントアップします。$SECURITY$MAJORがカウントアップしたときにだけゼロにリセットします。よって、ある$MAJORのある$SECURITYよりも大きい$SECURITYの数値は、$MINORの値に関わらず、よりセキュアであることを意味します。

バージョン番号の4つ目以降はJDKコードベースのdownstream consumers*2が自由に使用可能です。そうしたconsumerは、例えば、4つ目の番号を、対応関係にあるセキュリティリリースのフィックスに加えて、少数のクリティカルな非セキュリティフィックスを含む、パッチリリースを示すために使用します。

バージョン番号の数値は別の数値とはピリオドで区切った数値で比較可能で、たとえば、9.9.19.10.0より小さいです。もしピリオド分割後の要素数が比較対象のそれよりも少ない場合、その要素はゼロと見なします。たとえば、9.1.29.1.2.0と見なし、9.1.2.1より小さいです。

Version strings

バージョン文字列(version string)とは、上で説明したバージョン番号$VNUMで構成され、オプションでプレリリース(pre-release)やビルド情報(build information)を追加し、フォーマットは以下となります。

$VNUM(-$PRE)?(\+$BUILD)?(-$OPT)?
  • $PRE, 正規表現([a-zA-Z0-9]+) --- プレリリース識別子(A pre-release identifier)。たとえばeaは、early-accessを意味し、これは内部の開発者のビルド用で、internalないし潜在的に不安定で、開発中に早期リリースしたものを意味します*3
    二つのバージョン文字列を比較する場合、プレリリース識別子を持つ文字列は、プレリリース識別子を持たないが$VNUM部分は等しいもの未満に常になります。プレリリース識別子は、数値のみで構成される場合には数値順に比較され、そうでない場合は辞書式になります。
  • $BUILD, 正規表現([1-9][0-9]*) --- ビルド番号(build number)は、promoted build*4の度にカウントアップします。
    二つのバージョン文字列を比較する場合、もし$BUILDがある場合は、常に無視されます。
  • $OPT, 正規表現([-a-zA-Z0-9]+) --- 必要な場合に追加する、付加的なビルド情報(Additional build information)です。internalビルドの場合、しばしばビルドの日付と時刻が含まれます。
    二つのバージョン文字列を比較する場合、もし$OPTがある場合は、常に無視されます。

short version stringは、しばしば省略表現に使われるもので*5、末尾のゼロ要素を削除して、$MAJOR.$MINOR.$SECURITYを単純化したものです。つまり、$SECURITYの値がゼロの場合には削除され、$MINOR$SECURITYの値が両方ともゼロの場合には$MINORは削除されます。short version stringが$PREで終わるかどうかはオプションです。

以下の比較表はJDK 9における将来的なバージョン文字列で、既存と提案フォーマットとがそれぞれ載せてあります。

                      Existing                Proposed
Release Type    long           short    long           short
------------    --------------------    --------------------
Early Access    1.9.0-ea-b19    9-ea    9.0.0-ea+19    9-ea
Major           1.9.0-b100      9       9.0.0+100      9
Security #1     1.9.0_5-b20     9u5     9.0.1+20       9.0.1
Security #2     1.9.0_11-b12    9u11    9.0.2+12       9.0.2
Minor #1        1.9.0_20-b62    9u20    9.1.2+62       9.1.2
Security #3     1.9.0_25-b15    9u25    9.1.3+15       9.1.3
Security #4     1.9.0_31-b08    9u31    9.1.4+8        9.1.4
Minor #2        1.9.0_40-b45    9u40    9.2.4+45       9.2.4

参考までに、以下の比較表は、JDK 7のアップデートとセキュリティリリースのバージョン文字列を新フォーマットで仮に表示した場合を示しています。

                          Actual               Hypothetical
Release Type        long           short    long          short
------------        --------------------    -------------------
Security 2013/04    1.7.0_21-b11    7u21    7.4.10+11    7.4.10
Security 2013/06    1.7.0_25-b15    7u25    7.4.11+15    7.4.11
Minor    2013/09    1.7.0_40-b43    7u40    7.5.11+43    7.5.11
Security 2013/10    1.7.0_45-b18    7u45    7.5.12+18    7.5.12
Security 2014/01    1.7.0_51-b13    7u51    7.5.13+13    7.5.13
Security 2014/04    1.7.0_55-b13    7u55    7.6.14+13    7.5.14
Minor    2014/05    1.7.0_60-b19    7u60    7.6.14+19    7.6.14
Security 2014/07    1.7.0_65-b20    7u65    7.6.15+20    7.6.15

Dropping the initial 1 element from version numbers

この提案書では、JDKバージョン番号から最初の1要素の削除を実施します。よって、JDK 9の最初のリリースでは、バージョン番号は1.9.0.0ではなく9.0.0になる見込みです。

おおむね20年後くらいであれば、現行のバージョン番号スキーマの2番目の要素がJDKの"事実上の"$MAJORバージョン番号のままなのは確かでしょう。$MAJORは、重要な機能や互換性の無い変更を行う際に、カウントアップします。

我々は、現行スキーマの最初の要素を$MAJORバージョン番号とするよう処理することも可能でしたが、そうすると、既に多数の人間がJDK 9を1.9.0としているにも関わらずJDK 9のバージョン番号は2.0.0になってしまいます。これは誰にとってもよろしくないです。

もし最初の1をそのままにするとしたら、JDKのバージョン番号はSemantic Versioning規約に違反し続けることになり、また、はじめてJavaを知る開発者ももまた1.99の違いに混乱し続けることになります。

最初の1を削除することにはリスクが存在します。バージョン番号比較には多数の方法があるため、あるものは正しく動き、またあるものは動かなくなる可能性があります。

  • バージョン番号比較の既存コードが、要素をパースして数値的に各要素を比較する方法の場合、動作します。その理由は、9は1より大きいためです。たとえば、9.0.01.8.0より新しいと見なせます。
  • バージョン番号比較の既存コードが、最初の要素が1であればスキップする方法の場合も、動作します。その理由は、新しいスキーマでは最初の要素が1となることは決して無いためです。
  • バージョン番号比較の既存コードが、最初の要素は1と仮定して常に2番目の要素へスキップする場合、不正確な動作になります。例えば、こうしたコードは9.0.11.8.0より前のバージョンと見なしてしまいます。

事例証拠が示すこととしては、上記のうち三番目のカテゴリに属するコードはほとんどありませんが、我々は反証データも歓迎しています。

API

バージョン番号の比較・検証・パースを行うシンプルなJDK固有のJava APIの定義を行い、それはおおむね以下のような内容となります。

package jdk.util;

import java.io.Serializable;
import java.util.Optional;

public class Version 
    implements Comparable<Version>, Serializable
{

    public static Version parse(String);
    public static Version current();

    public int major();
    public int minor();
    public int security();

    public List<Integer> version();
    public Optional<String> pre();
    public Optional<Integer> build();
    public Optional<String> optional();

    public int compareTo(Version o);

}

同等なC APIの定義も行い、改訂されたjvm_version_info structになると思われます。

JDKバージョン文字列を比較・検査するJDKの全コードは上記APIを使用するように修正します。JDKバージョン文字列を比較・検査するライブラリやアプリケーションの開発者は上記APIの使用が推奨されるようになります。

Testing

バージョン文字列のシンタックスとセマンティクスの変更は全コンポーネントの領域で広範囲なテストが必要となります。JDKバージョン文字列とは無関係な既存テストはパスする必要があります。

*1:incompatible changes may be made when justified.が原文。when justifiedを「正当な理由があれば~」とやや意訳してしまったが、やや訳に自信が無い。

*2:JDKのコードに更に独自の改修を加えるユーザ、ってことなんだろうけど、日本語だと何というのだろうか

*3:Typically ea, for an early-access release that's under active development and potentially unstable, or internal, for an internal developer build.が原文。ほとんど直訳だけど、固有名詞ばっかなんで……

*4:日本語だとなんて訳すんだ……?

*5:in less formal contextsが原文。上手い言い回しが思い浮かばなかったので、思い切って訳も色々と省略した表現にしておいた