はじめに
前回の記事でrebarでやったことを今度はElixirのビルドツールmixを使ってやってみましょう。
mixにおける依存アプリケーション・バージョン衝突問題の回避
ElixirはErlangのアプリケーションをそのまま読み込めますので前回の記事で使用したErlangのサブアプリケーションを今度はElixirプロジェクトが必要としているという場面を想定しましよう。サブ・アプリケーションがErlangなのかElixirなのかは本質的に重要ではありません。
に空のElixirプロジェクトを組んでみました。Elixirはビルド時にビルドツールのmixやテストツールやら開発に必要なもろもろが既に組み込まれており、ほとんどの開発者がこのプロジェクトと同じようなフォーマットで開発を進めていくことと思います。
Erlang/rebarプロジェクトのrebar.configにあたるものが、Elixir/mixではmix.exsにあたります。
defmodule SampleMainElixir.Mixfile do
use Mix.Project
def project do
[app: :sample_main_elixir,
version: "0.0.1",
elixir: "~> 1.0",
deps: deps]
end
defp deps do
[{:sample_sub1, git: "https://github.com/massn/sample_sub1.git", tag: "v0.0.1"},
{:sample_sub2, git: "https://github.com/massn/sample_sub2.git", tag: "v0.0.1"}]
end
end
depsという関数が返すリストに依存アプリケーションが書かれています。書き方が少々Erlangと違いますがなんとなくわかると思います(糖衣構文なども含まれていますが、表現しているのはErlangと互換性のあるデータ構造です)。依存アプリケーションをダウンロードしてみましょう。
$ ./mix deps.get
このコマンドでmix.lockというファイルにダウンロードされるべき全てのアプリケーションのバージョンが書き込まれ、同時にダウンロードが行われます。RubyでGemパッケージを使うときのGemfile.lockのようなものです。mix.lockファイルを見てみましょう。
%{"cowboy": {:git, "https://github.com/ninenines/cowboy.git", "ee3ad5e51005b453230804036e092e1d2ceebe4f", [ref: "0.10.0"]},
"cowlib": {:git, "git://github.com/extend/cowlib.git", "e2ffefe828b918486e2cd76e44c54ae9b62c616e", [ref: "0.6.2"]},
"ranch": {:git, "git://github.com/extend/ranch.git", "3189ef2d47843945efc96c224963380462c33d40", [ref: "0.10.0"]},
"sample_sub1": {:git, "https://github.com/massn/sample_sub1.git", "1700f7e6d4d708351a2c02274643646808e35911", [tag: "v0.0.1"]},
"sample_sub2": {:git, "https://github.com/massn/sample_sub2.git", "0fe69055e0da389d4b28257e4db44495d1a3e641", [tag: "v0.0.1"]}}
すべての依存アプリケーションの使うべきバージョン、さらにはコミットIDまで列挙されてますね。mix.lockファイルはmix.exsを元にしており、その時点でそこから推測できるアプリケーション情報を持っています。例えばあるアプリケーションのmasterブランチがmix.exsに指定されていると、そのときのmasterブランチのコミットIDが記載されるわけです。このロックファイルをgit管理することで開発者が使った依存アプリケーションの環境をそのまま再現することが可能になります。
ところで前回問題にしたcowboyはどのバージョンが使われているのでしょう?sample_sub2が要求する0.10.0です。うーん、今度はdepsが返すリストの後ろの方が優先順位が高いようです。。。わかりにくい。できればリストの順番なんか気にしたくない。
ここで、mix deps.checkというコマンドを使ってみましょう。
$ ./mix deps.check
Unchecked dependencies for environment dev:
* cowboy (https://github.com/ninenines/cowboy.git)
different specs were given for the cowboy app:
> In deps/sample_sub2/rebar.config:
{:cowboy, ~r/.*/, [git: "https://github.com/ninenines/cowboy.git", ref: "0.10.0"]}
> In deps/sample_sub1/rebar.config:
{:cowboy, ~r/.*/, [git: "https://github.com/ninenines/cowboy.git", ref: "0.8.0"]}
Ensure they match or specify one of the above in your SampleMainElixir.Mixfile deps and set `override: true`
おお。
「deps内のアプリケーションが同一アプリケーションの別バージョンを要求しているよ。そいつらのバージョンを合わせるか、depsにoverride: trueをセットしてアプリケーションを要求してね」
というありがたいアドバイスが。正しくその通りです。mix.exsを以下のように修正しましょう( https://github.com/massn/sample_main_elixir/tree/v0.0.2 )。
defmodule SampleMainElixir.Mixfile do
use Mix.Project
def project do
[app: :sample_main_elixir,
version: "0.0.1",
elixir: "~> 1.0",
deps: deps]
end
defp deps do
[{:cowboy, git: "https://github.com/ninenines/cowboy.git", ref: "0.10.0", override: true},
{:sample_sub1, git: "https://github.com/massn/sample_sub1.git", tag: "v0.0.1"},
{:sample_sub2, git: "https://github.com/massn/sample_sub2.git", tag: "v0.0.1"}]
end
end
バージョン競合が起きているアプリケーションについては、明示的にこのバージョンを使用しろと書くわけです。override: trueを書かないと再びmix deps.checkでエラーが出てしまうのでお忘れなく。これで./mix deps.checkをするとエラーが出ず、コンパイルが始まります。
まとめ
mixでの開発において依存アプリケーションのバージョン指定については以下のような指針を設けるのがよさそうです。
全ての依存アプリケーションにおいてバージョン衝突があるかないかをmix deps.checkコマンドで確認する
バージョン衝突が起きた場合は、バージョンをそろえるか、一番上の階層の(依存アプリケーションがmix.exsを持つこともある)mix.exsに使用するアプリケーションのバージョンを明示する(override: trueを忘れずに)
生成されたmix.lockファイルをgit管理などして、全ての使用アプリケーションのバージョンを明示する
これはrebarよりいい感じですね。
つづく