オープンソースこねこね

Webプログラミングなどについてあれこれ。

Composerがパッケージのstabilityを解決するしくみ

PHPとComposerで先日composer/composerのdev-masterに依存したプログラムを作っていたら、composer installのときに以下のようなエラーがでてインストールできない問題にぶちあたりました。

Your requirements could not be resolved to an installable set of packages.

Problem 1
    - kohkimakimoto/altax v3.0.6 requires composer/composer dev-master -> no matching package found.
    ...

解決方法はcomposer.jsonに"minimum-stability": "dev"と"prefer-stable": trueを指定するか、対象のパッケージに"composer/composer": "@dev"のようなstabilityフラグをつければOKでした。

さて、この件に関連する日本語情報があまりなかったのですが、 根本の仕組み(Composerがパッケージのstabilityを判断してダウンロードする仕組み)を丁寧に書いたブログをみつけたので、以下和訳してみました。

Composerのスタビリティフラグ

原題:Composer Stability Flags

https://igor.io/2013/02/07/composer-stability-flags.html

今のところcomposerのサポートにやってくる最もよくある問い合わせは、スタビリティ(パッケージの安定性、stability)がどう解決されるのかよくわからないというものだ。 よくこのケースは以下のような問い合わせになる。

パッケージB:dev-masterに依存するパッケージA:dev-masterを要求(require)したら、composerがパッケージBが見つからないというんだ。

ルートパッケージ

ルートパッケージはメインのcomposer.jsonファイルのことだ。これはcomposer installを実行するときにいるディレクトリと同じディレクトリ内にある。 多くのcomposer.jsonのフィールドはルートオンリーで、これはルートパッケージ内で指定されたときだけ影響をもつということだ。

ルートパッケージはコンテキストだ。 あなたが自分のパッケージのディレクトリ内でパッケージAに依存しているといった場合、あなたのパッケージがルートパッケージとなる。 パッケージAのディレクトリにcdしたらAがルートパッケージだ。

スタビリティはルートパッケージで決定される。そしてルートパッケージのみで決定される。 これを忘れないようにして、ちょっと考えてみよう。

Composerはユーザの手にわたる依存物がどのようなスタビリティか判断をする。 ユーザとしてあなたは開発版、ベータ、または安定版のリリースを使いたいか決める。

最低限のスタビリティ(minimum-stability)

このスタビリティの判断はルートパッケージのminimum-stabilityフィールドに基づいて行われる。これはルートオンリーだ。スタビリティフラグのデフォルト値を定義し、下限として振る舞う。

f:id:kohkimakimoto:20140403113456p:plain

これは引き下げることのできるルーラーで、デフォルトは"stable"をさしている。 しかし引き下げると、より低いスタビリティフラグを示すことができる。

minimum-stabilityはすべての制約のためのデフォルトの安定性を定義する。

スタビリティの解決

それでは、つぎのようなシナリオを考えてみよう。ルートパッケージがA:dev- masterを要求していて、 それがさらにB:dev-masterを要求している場合だ。

f:id:kohkimakimoto:20140403114304p:plain

ルートパッケージは以下のようになる

{
    "require": {
        "A": "dev-master"
    }
}

Composerは以下のステップを踏む:

  • minimum-stabilityを決定:このケースではフィールドが定義されていないのでデフォルト値が設定される。これは"stable"だ。
  • Aはdev-masterというバージョン制約をもっている。dev-プリフィクスがついているので、これはdevバージョンであることがわかる。そしてdevバージョンは"dev"スタビリティをもっている。ルートパッケージで定義されたこのdevバージョン制約のため、暗黙的に@devスタビリティフラグを得る。
  • Aは制約A:dev-master@devをもっているので、このバージョンはマッチしてcomposerはリンクする。AはBにdev-masterという制約つきで依存している。これはdev-プリフィクスを持っている、よって"dev"スタビリティをもっている。

    ところが、この制約はパッケージAの中で定義されていて、かつルートパッケージではないので、暗黙的に@devスタビリティフラグを得ることはできない。その代わりにminimum-stabilityを継承する。これは"stable"だ。よって、解決される制約はB:dev-master@stableとなる。

ここが障害のポイントだ。なぜならB:dev-master@stableはどうやっても解決できないからだ。composerは与えられたスタビリティの範囲でパッケージBが見つからないとこたえるのだ。

この問題に対処する方法の一つはminimum-stabilityを"dev"に下げることだ。 しかしこれは通常とてもよくないアイデアだ。これはすべての制約に適用されてしまい、 その結果、すべてのパッケージを不安定なバージョンで取得してしまう。

だからお願いだ。それをしないでくれ。

スタビリティフラグ

代わりにスタビリティフラグを使おう。

フラグはバージョン制約の一部として定義される。スタビリティはルートパッケージでのみで決定されるので、フラグもまたルートオンリーだ。 依存パッケージ内で定義されたフラグは単純に無視される。

フラグは不安定版パッケージを指定するホワイトリストとして使うことができる。このケースにおいて、私はBをホワイトリストに追加したい。このようにする:

{
    "require": {
        "A": "dev-master",
        "B": "@dev"
    }
}

注目すべきなのは、実際のバージョンをルートパッケージ内で定義していない点だ。 これはルートパッケージはインストールされるBのバージョンを扱わないということを意味する。 バージョンの決定は指定する制約をもっているAに委譲している。

これによって、もしAがBへの依存をdev-masterから~1.0またはそれ以外に変更したとしても、ルートパッケージはなにも変更する必要がなくなる。

Silexの例

この動作が実際にどのようなものか、よりアイデアを得るために、silexを例に見てみよう。

これを書いている現時点で、silexは安定バージョンがない(訳者注:今は1.2の安定バージョンがありますね)。インストールするために@devフラグを追加する必要がある:

{
    "require": {
        "silex/silex": "1.0.*@dev"
    }
}

Silexは1.0の開発バージョンである1.0.x-devバージョンのみがある。

Silexのすべての依存物は安定バージョンがある。これはデフォルトでv2.1.7の多数のsymfonyコンポーネントとv1.0.1のpimpleを得るということだ。

もし数日前にリリースされたsymfonyコンポーネントのv2.2.0-RC1バージョンを試したくなった場合、 それを以下のようにホワイトリストにすることができる:

{
    "require": {
        "silex/silex": "1.0.*@dev",
        "symfony/event-dispatcher": "@RC",
        "symfony/http-foundation": "@RC",
        "symfony/http-kernel": "@RC",
        "symfony/routing": "@RC"
    }
}

バージョンをすべて指定するのは面倒なので、minimum-stabilityを下げることもできる。 この場合それはOKだ。あなたが望まない不安定バージョンがインストールされることがないからだ。

{
    "minimum-stability": "RC",
    "require": {
        "silex/silex": "1.0.*@dev"
    }
}

prefer-stable

このポストを書いてしばらくした後、composerはprefer-stableという機能をリリースした。 もし、依存物のスタビリティを把握したくない場合、単にprefer-stableフィールドをルートパッケージで使うことができる。 Composerは可能なもので最も安定した依存物を導きだす。

これはとても便利でほとんどの場合それで十分だ。しかし、私はあなたが本当に必要としているスタビリティを考えることをお勧めする。明示的にそれを定義することもね。あなたは便利さと制御をトレードしてるかもしれない。

結論

composerがどのようにスタビリティを解決しているか。不安定バージョンを取得するためにスタビリティフラグどのように使うことができるか。この記事があなたのよりよい理解に役立てば、幸いだ。

でも覚えておいてくれ:スタビリティフラグを必要とするもっともよくある理由は、あなたの依存物のメンテナが安定板をタグ付けしない理由によるものだ。彼らにブランチエイリアスの追加とタグリリースをさせるため、いってやってくれ。彼らがそれを行ったらすぐに、スタビリティフラグを捨てて、またハッピーになれるよ。

元記事はこちら