セマンティック バージョニング 2.0.0

概要

 バージョンナンバーは、メジャー.マイナー.パッチ とし、バージョンを上げるには、

  1. APIの変更に互換性のない場合はメジャーバージョンを、
  2. 後方互換性があり機能性を追加した場合はマイナーバージョンを、
  3. 後方互換性を伴うバグ修正をした場合はパッチバージョンを上げます。

 プレリリースやビルドナンバーなどのラベルに関しては、メジャー.マイナー.パッチ の形式を拡張する形で利用することができます。

導入

 ソフトウェア・マネージメントの世界には、「依存性地獄」と呼ばれる恐ろしいものがあります。あなたのシステムが大きく成長すればするほど、さまざまなパッケージを組み込めば組み込むほど、自分が地獄の底にいることにいつか気づくでしょう。

 多くの依存性を有しているシステムにとって、新しいバージョンがリリースされることは悪夢でしかありません。厳密に依存関係を指定してしまうと、システムはバージョン・ロック(すべての依存パッケージを新しくしない限り、アップグレードできないこと)の危機にさらされてしまいます。反対に、依存指定を緩く管理しすぎると、バージョンが複雑に絡まり合い、痛い目にあうことは避けられないでしょう(合理性よりも将来のバージョンとの互換性を気にすることになる)。依存性地獄とは、あなたのプロジェクトでバージョン・ロックまたはバージョン混乱に陥ることで、プロジェクトに支障をきたすことを指します。

 この問題の解決策として、私はシンプルなルールセットとバージョン・ナンバーをどのように割り当て、バージョンを上げていくのかについての要件を提案します。これらのルールは既存のクローズドまたはオープンソースプロジェクトで普及している一般的な(必ずしもそうであるとは限りませんが)プラクティスをもとに作られています。このシステムを利用するために、まずはパブリックなAPIを宣言する必要があります。これはドキュメントに記載しても、コード自体で表現しても構いません。とにかく、APIが明確かつ正確であることは非常に重要です。パブリックなAPIを宣言したら、それを変更する際にはルールに従ってバージョン番号を上げなければなりません。つまり、X.Y.Z(メジャー.マイナー.パッチ)のバージョン形式を遵守しなければなりません。APIに影響を及ぼさないバグ修正はパッチバージョンを、後方互換性を保ちつつAPIを変更・追加した場合はマイナーバージョンを、後方互換性のないAPIの変更はメジャーバージョンを上げます。

 私はこのシステムを『セマンティック バージョニング』と呼び、このスキームに従えば、あるバージョンのコードが次のバージョンへの変更された際に何が変更されたのかユーザーに伝えることができます。

セマンティック バージョニング仕様書 (SemVer)

 この文書における各キーワード「しなければならない(MUST)」、「してはならない(MUST NOT)」、「要求されている(REQUIRED)」、「することになる(SHALL)」、「することはない(SHALL NOT)」、「する必要がある(SHOULD)」、「しないほうがよい(SHOULD NOT)」、「推奨される(RECOMMENDED)」、「してもよい(MAY)」、「選択できる(OPTIONAL)」は、RFC 2119に記載されている内容に従い解釈してください。

  1. セマンティック バージョニングを適用するソフトウェアはパブリックAPIを宣言しなければなりません(MUST)。このAPIはコード自体で表現されているかもしれませんし、明確に文書として存在してるかもしれません。どちらにせよ、正確かつ漏れがないようにするべきです(SHOULD)。

  2. 通常のバージョンナンバーは、X.Y.Zの形式にしなければなりません(MUST)。このときX、Y、Zは非負の整数であり(MUST)、各数値の先頭にゼロを配置してはなりません(MUST NOT)。Xはメジャーバージョン、Yはマイナーバージョン、Zはパッチバージョンを表します。各バージョンは数値的にバージョンアップしなければなりません(MUST)。例:1.9.0 -> 1.10.0 -> 1.11.0。

  3. 一度パッケージをリリースしたのなら、そのバージョンのパッケージのコンテンツは修正してはなりません(MUST NOT)。いかなる修正も新しいバージョンとしてリリースしなければなりません(MUST)。

  4. メジャーバージョンのゼロ(0.y.z)は初期段階の開発用です。いつでも、いかなる変更も起こりえます(MAY)。この時のパブリックAPIは安定していると考えるべきではありません(SHOULD NOT)。

  5. バージョン1.0.0はパブリックAPIを定義します。このリリース後のバージョンナンバーの上げ方に関しては、パブリックAPIがどのくらい変更されるのかによって決まります。

  6. パッチバージョン Z (x.y.Z | x > 0)は、後方互換性を保ったバグ修正を取り込んだ場合のみ、上げなければなりません(MUST)。バグ修正とは間違った振る舞いを修正する内部の変更のことを指します。

  7. マイナーバージョン Y (x.Y.z | x > 0)は、後方互換性を保ちつつ機能性をパブリックAPIに追加した場合、上げなければなりません(MUST)。また、いかなるパブリックAPIも廃止予定としたのなら、上げなければなりません(MUST)。プライベートコード内での新しい機能の追加や改善を取り込んだ場合は、上げてもよいです(MAY)。その際にパッチレベルの変更も含めてもよいです(MAY)。マイナーバージョンを上げた際にはパッチバージョンを0にリセットしなければなりません(MUST)。

  8. メジャーバージョン X (X.y.z | X > 0)は、パブリックAPIに対して後方互換性を持たない変更が取り込まれた場合、上げなければなりません(MUST)。その際にマイナーおよびパッチレベルの変更も含めてもよいです(MAY)。メジャーバージョンを上げた際には、パッチおよびマイナーバージョンを0にリセットしなければなりません(MUST)。

  9. プレリリースバージョンは、パッチバージョンの直後にハイフンとドットで区切られた識別子を追加することで表現してもよいです(MAY)。識別子は必ずASCII英数字とハイフン [0-9A-Za-z-] でなければなりません(MUST)。識別子は空であってはなりません(MUST NOT)。数値の識別子はゼロから始めてはなりません(MUST NOT)。プレリリースバージョンは関連する通常のバージョンよりも低い優先度です。プレリリースバージョンは、不安定であり、関連する通常のバージョンが示す要件と互換性を満たさない可能性があります。例:1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92。

  10. ビルドメタデータはパッチまたはプレリリースバージョンの直後にプラス記号とドットで区切られた識別子を追加することで表現してもよいです(MAY)。識別子は必ずASCII英数字とハイフン [0-9A-Za-z-] でなければなりません(MUST)。識別子は空であってはなりません(MUST NOT)。バージョンの優先度を決める際にはビルドメタデータは無視されなければなりません(MUST)。つまり、2つのビルドメタデータだけが違うバージョンは、同じ優先度ということです。例:1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85。

  11. バージョン同士をどのように比較するのかは優先度によって決まります。優先度はメジャー、マイナー、パッチ、プレリリース識別子の順番(ビルドメタデータは優先度に関して考慮しない)で分けて評価されなければなりません(MUST)。優先度は、各識別子を左から右に比較して最初の違いによって評価します。以下のように、メジャー、マイナー、パッチバージョンと常に数値的に比較します。例:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。メジャー、マイナー、パッチが同じ場合、プレリリースバージョンを持っている方が通常のバージョンよりも低い優先度です。例:1.0.0-alpha < 1.0.0。同じ、メジャー、マイナー、パッチを持つプレリリースバージョンの優先度の決定は、ドットで区切れた識別子を左から右に、異なるところが見つかるまで比較し決定しなければなりません(MUST)。数値のみで構成される識別子は数値的に比較され、文字列やハイフンを含む識別子はASCIIソート順に辞書的に比較されます。数値的な識別子は常に数値的でない識別子よりも低い優先度です。もし先行する識別子が同じ場合、プレリリースのフィールドが小さいセットよりも大きいセットのほうが高い優先度です。例:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0。

SemVerバージョンを表すバッカス・ナウア記法

<valid semver> ::= <version core>
                 | <version core> "-" <pre-release>
                 | <version core> "+" <build>
                 | <version core> "-" <pre-release> "+" <build>

<version core> ::= <major> "." <minor> "." <patch>

<major> ::= <numeric identifier>

<minor> ::= <numeric identifier>

<patch> ::= <numeric identifier>

<pre-release> ::= <dot-separated pre-release identifiers>

<dot-separated pre-release identifiers> ::= <pre-release identifier>
                                          | <pre-release identifier> "." <dot-separated pre-release identifiers>

<build> ::= <dot-separated build identifiers>

<dot-separated build identifiers> ::= <build identifier>
                                    | <build identifier> "." <dot-separated build identifiers>

<pre-release identifier> ::= <alphanumeric identifier>
                           | <numeric identifier>

<build identifier> ::= <alphanumeric identifier>
                     | <digits>

<alphanumeric identifier> ::= <non-digit>
                            | <non-digit> <identifier characters>
                            | <identifier characters> <non-digit>
                            | <identifier characters> <non-digit> <identifier characters>

<numeric identifier> ::= "0"
                       | <positive digit>
                       | <positive digit> <digits>

<identifier characters> ::= <identifier character>
                          | <identifier character> <identifier characters>

<identifier character> ::= <digit>
                         | <non-digit>

<non-digit> ::= <letter>
              | "-"

<digits> ::= <digit>
           | <digit> <digits>

<digit> ::= "0"
          | <positive digit>

<positive digit> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

<letter> ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J"
           | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T"
           | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d"
           | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n"
           | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x"
           | "y" | "z"

なぜセマンティック バージョニングを使用するのか?

 このアイデアは新しいものでもなければ、革新的なものでもありません。実際、みなさんも似たような取り組みを既におこなっているかもしれません。問題は「似ている」のでは不十分だということです。正式な仕様書による取り決めがなければ、バージョンナンバーは依存性の管理において基本的には無意味です。上記のアイデアに対して名前と正確な定義を与えることよって、あなたの開発するソフトウェアにおいて、あなたの意図がユーザーに対して伝わりやすくなることでしょう。一度、これらの意図を正確にしてしまえば、柔軟な(しかし、柔軟すぎてはいけない)依存性の仕様を作ることができます。

 単純な例として、セマンティック バージョニングがどのように依存性地獄を過去のものとするかについて説明します。「Firetruck」と呼ばれるライブラリについて考えてみましょう。それはセマンティック バージョニングされた「Ladder」というパッケージを必要とします。Firetruckを作成した時、Ladderはバージョン3.1.0でした。Firetruckは、バージョン3.1.0時に導入されたいくつかの機能を使用しているので、Ladderが3.1.0以上4.0.0未満の範囲で安全に依存性を指定できます。Ladderのバージョン3.1.1と3.2.0が利用可能になった時、それらをパッケージ管理に取り込んでリリースすることができ、それらが既存の依存するソフトウェアと互換性があるということは明確です。

 賢明な開発者であれば、もちろんパッケージがアップグレードされたのならその機能を使ってみたいと思うはずでしょう。ただ、現実は混沌としていて、我々ができることといったら、慎重になることくらいです。セマンティック バージョニングを実践することで、新しい依存パッケージを巻き込むことなく、まともな方法でリリース、アップグレードすることができ、手間と時間を節約してくれることでしょう。

 もし全面的に同意できると感じたのなら、セマンティック バージョニングを実践していることを宣言し、ルールを守って下さい。それからあなたのREADMEからこのWebサイトにリンクしてください、そうすれば、他の人がこのルールを知り、役立てることができるでしょう。

FAQ

0.y.zのような初期の開発フェーズにおけるバージョンの取り扱いはどのようにすべきでしょうか?

 一番簡単な方法は0.1.0からで開発版をリリースし、その後のリリースのたびにマイナーバージョンを上げていけばよいでしょう。

1.0.0のリリースはいつすべきでしょうか?

 もし既にプロダクション用途であなたのソフトウェアが利用されているのなら、それは1.0.0であるべきでしょう。またもし安定したAPIを持ち、それに依存しているユーザーが複数いるのなら、それは1.0.0であるべきでしょう。もし後方互換性について多大な心配をしているのなら、それは1.0.0であるべきでしょう。

高速開発や高速イテレーションに悪影響を与えませんか?

 メジャーバージョンがゼロの場合、それは高速開発を意味しています。もし、毎日APIを変更しているのなら、0.y.zのバージョンのままにすべきか、開発ブランチとして切り分け、次のメジャーバージョンアップのために開発すべきです。

パブリックAPIに対して後方互換性を保たない、ほんの些細な変更があった際もメジャーバージョンアップをしなければならないのなら、42.0.0のようなバージョンにすぐになってしまわないですか?

 これは責任ある開発と深い洞察のある質問です。多くの依存されているコードを持つソフトウェアにおいて、非互換な変更を気軽に取り込むべきではありません。アップグレードする度にかかるコストは無視できないものでしょう。非互換な変更をリリースするためにメジャーバージョンを上げることは、変更における悪影響を思い知ることになるでしょう。加えて、費用対効果を評価して判断すべきでしょう。

すべてのパブリックAPIに関してドキュメントを書くのは重労働です。

 ユーザーに使用されることを前提としたソフトウェアに適切なドキュメントを用意することはプロフェッショナルな開発者としての当然の責任です。ソフトウェアの複雑性を管理することはプロジェクトをスムーズに続けていくために非常に重要なことで、もしあなたのソフトウェアの使い方や、どのメソッドが安全に呼び出せるのかを誰も知らないのは望ましい状況とは言えません。セマンティック バージョニングと適切に定義されたパブリックAPIを宣言することは長期的に見ればメリットが大きいでしょう。

もし誤って後方互換性のない変更をマイナーバージョンとしてリリースした場合、どうすればよいでしょうか?

 セマンティック バージョニングの仕様に違反したのならすぐに問題を修正してください。つまり後方互換性を復元し、新しくマイナーバージョンをリリースして下さい。このような状況下においてもリリースされたバージョンナンバーを修正してはなりません。適切な対応が完了したのなら、次は違反したバージョンを記録し、それをユーザーに知らせましょう。

もし、パブリックAPIの変更なしに自分のパッケージの依存性をアップデートしてしまったら、どうすべきでしょうか?

 それ自体はパブリックAPIに影響しませんので安心してください。パッケージとして明らかに同じ依存性を持つソフトウェアは自身の依存仕様を定義すべきですし、作成者はいかなるコンフリクトも見逃さないでしょう。その変更がパッチレベルなのかマイナーレベルなのかどうかは、パッケージの依存性をアップデートした理由がバグを修正するためか新しい機能を実装するためかどうかに依存します。後者のケースの場合は明らかにマイナーレベルの変更でしょう。

もしうっかりパブリックAPIをセマンティック バージョニングに対応していない形で変更したらどうなるでしょうか?(例:パッチリリースで主要なバグが発生した場合)

 最善の判断をしてください。もしパブリックAPIの動作が変わることで大いに影響するユーザーがいる場合、厳密にはパッチリリースであると考慮されるかもしれませんが、その後にメジャーバージョンをリリースするのがベストかもしれません。セマンティック バージョニングはバージョンがどのように変更されるのかということを伝えるためのものであるということを注意して下さい。

どのように非推奨機能を扱えばよいでしょうか?

 既存機能を廃止予定にするのはソフトウェア開発においては普通の事であり、開発を進める上で頻繁に必要となります。パブリックAPIの一部を非推奨にしたい場合、2つのことをすべきです。第一にユーザーに知らせるためにドキュメントを更新して下さい。次に非推奨機能を残したまま新しいマイナーバージョンをリリースして下さい。完全に非推奨機能を削除しメジャーバージョンをリリースする前に、ユーザーがスムーズに新しいAPIに移行できるように少なくとも1回のマイナーバージョン(非推奨機能を含んだ)をリリースして下さい。

SemVerのバージョン文字列に限度はありますか?

 いいえ、ありませんが良識ある判断をしてください。例えば255文字のバージョン文字列は過剰と言えるでしょうし、特定のシステムではそれ独自の文字列の限界値があることでしょう。

『v1.2.3』はセマンティック バージョンでしょうか?

 いいえ、『v1.2.3』はセマンティック バージョンではありません。しかしながら、セマンティック バージョンに接頭辞の『v』を付けるのは英語ではバージョン番号であることを示す一般的な方法です。バージョン管理では、『バージョン』を『v』と略すことがよくあります。たとえば git tag v1.2.3 -m" Release version 1.2.3 " では『v1.2.3』はタグ名であり、セマンティック バージョンは『1.2.3』です。

SemVer文字列をチェックするために推奨される正規表現(RegEx)はありますか?

 二つあります。一つは名前付きグループをサポートするシステム(PCRE [Perl Compatible Regular Expressions, 例: Perl, PHP and R]、Python、Go)のための正規表現です。

参考: https://regex101.com/r/Ly7O1x/3/

^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$

 もう一つは番号付きキャプチャグループを使った正規表現です(したがって、cg1 = メジャー、cg2 = マイナー、cg3 = パッチ、cg4 = プレリリース、cg5 = ビルドメタデータを意味します)。これはECMA Script(JavaScript)、PCRE(Perl Compatible Regular Expressions, 例: Perl, PHP and R)、Python、Goにおいて互換性があります。

参考: https://regex101.com/r/vkijKf/1/

^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$

著者について

セマンティック バージョニング仕様書の著者はTom Preston-Werner氏です。彼はGravatarの考案者であり、GitHubの共同創設者でもあります。

もしフィードバックがある場合は、GitHub上でissueを立てて下さい

ライセンス

Creative Commons ― CC BY 3.0