はじめに
前回の記事で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よりいい感じですね。
つづく