简介

文章目录
  1. 1. Bootstrapping
    1. 1.1. mix.exs
    2. 1.2. lib/my_project.ex
    3. 1.3. test/my_project_test.exs
    4. 1.4. test/test_helper.exs
  2. 2. 探索
  3. 3. 编译
  4. 4. 依赖
    1. 4.1. 源代码管理(scm)
    2. 4.2. 编译依赖
    3. 4.3. 重复性
    4. 4.4. 依赖任务
    5. 4.5. 依赖的依赖
  5. 5. 伞形项目
  6. 6. 环境

Elixir自带了几个应用使得编写和部署Elixir项目更加容易,其中Mix是关键.

Mix是一个提供了用于创建,编译,测试(很快就会有发布功能)的编译工具.Mix的灵感来自于Clojure的编译工具Leiningen,并且作者本人就是它的其中一个开发者.

在这一章,我们将学习如何用mix来创建项目,安装依赖.在之后的部分,我们将雪鞋如何创建OTP应用,和定制mix的任务.

Bootstrapping

要开始你的第一个项目,你只需要输入mix new 并把项目的路径作为参数.现在,我们将在当前目录创建一个被称为my_project的项目:

1
$ mix new my_project --bare

Mix将创建一个名为my_project的目录,包含一下这些内容:

1
.gitignore
README.md
mix.exs
lib/my_project.ex
test/test_helper.exs
test/my_project_test.exs

让我们来简单地看一下其中的一些.

  • 注意:Mix是一个Elixir的可执行文件.这意味着为了能够运行mix,elixir的可执行文件许需要在你的PATH里面.如果不是,你可以直接把脚本作为参数传递给elixir来执行:

    $ bin/elixir bin/mix new ./my_project

  • 注意你也能通过-S选项来让Elixir运行任何PATH里的脚本:

    $ bin/elixir -S mix new ./my_project

当用了-S, elixir会遍历PATH,寻找并运行那个脚本

mix.exs

这是包含了你项目配置的文件.它看起来是这样的:

1
defmodule MyProject.Mixfile do
  use Mix.Project

  def project do
    [app: :my_project,
     version: "0.0.1",
     deps: deps]
  end

  # Configuration for the OTP application
  def application do
    []
  end

  # Returns the list of dependencies in the format:
  # {:foobar, git: "https://github.com/elixir-lang/foobar.git", tag: "0.1"}
  #
  # To specify particular versions, regardless of the tag, do:
  # {:barbat, "~> 0.1", github: "elixir-lang/barbat"}
  defp deps do
    []
  end
end

我们的mix.exs定义了两个函数:

project, 用来返回项目的配置比如项目名称和版本,和application,用来产生一个被Erlang运行时管理的Erlang应用.在这一章,我们将谈一谈函数project.我们将在下一章详谈application函数.

lib/my_project.ex

这个文件包含了一个简单的模块,它定义了我们代码的基本结构:

1
defmodule MyProject do
end

test/my_project_test.exs

这个文件包含了项目的一个测试用例:

1
defmodule MyProjectTest do
  use ExUnit.Case

  test "the truth" do
    assert true
  end
end

有几点请注意:

  • 注意这个文件是一个Elixir的脚本文件(.exs).作为一个约定,我们不需要在运行之前编译测试.
  • 我们定义了一个测试模块MyProjectTest,用MyProjectTest来注入默认的行为,并定义了一个简单测试.你可以在ExUnit那一章学习到有关测试框架的更多内容.

test/test_helper.exs

我们将查看的最后一个文件是test_helper.exs,它的任务是启动测试框架:

1
ExUnit.start

探索

现在我们已经创建了新项目,接下去做什么?要了解还有其他什么命令可以使用的话,运行help任务:

1
$ mix help

它将会打印出所有可用的任务,运行mix help TASK可获取更多的信息.

运行其中一些命令试试,比如mix compilemix test,在你的项目里运行看看会发生什么.

编译

Mix可以为我们编译项目.默认的设置是用lib/放源代码,ebin/放编译后的beam文件.你无需提供任何的编译相关的设置,但如果你决定这么做,有一些选项可以用.例如,如果你打算把你的编译后的beam文件放在ebin之外的文件夹里,只需要在mix.exs里设置:compile_path:

1
def project do
  [compile_path: "ebin"]
end

总的来说,Mix会尽力表现的聪明一些,只在必须的时候编译.

注意在你第一次编译之后,Mix会在你的ebin文件夹里产生了一个my_project.app文件.这个文件里定义的Erlang应用是用到了你的项目中的application函数里的内容.

这个.app文件存储在和应用有关的信息,它的依赖,它所依赖的模块㩐等.每次你用mix运行命令的时候,这个应用会自动被启动,我们将在下一章学习如何配置它.

依赖

Mix也能用来管理依赖.依赖应被列在项目配置中,例如:

1
def project do
  [app: :my_project,
   version: "0.0.1",
   deps: deps]
end

defp deps do
  [{:some_project, ">= 0.3.0"},
   {:another_project, git: "https://example.com/another/repo.git", tag: "v1.0.2"}]
end

注意: 虽然并非必须,常见的做法是把依赖分散到它们自己的函数里.

某个依赖有一个原子来表示,跟着是一个需求和一些选项.在默认情况下,Mix使用hex.pm来获取依赖,但它也能从git库或直接从文件系统来获取.

当我们使用Hex, 你必须在需求里指定所接受的依赖的版本.它支持一些基本的操作符,例如>=,<=,>,==:

1
# Only version 2.0.0
"== 2.0.0"

# Anything later than 2.0.0
"> 2.0.0"

需求也支持用andor表达复杂的情况:

1
# 2.0.0 and later until 2.1.0
">= 2.0.0 and < 2.1.0"

类似上面的例子非常地常见,所以它也能用简单的方式表达:

1
"~> 2.0.0"

注意为git库设置版本需求不会影响到取出的分支和标签,所以类似下面这样的定义是合法的:

1
{ :some_project, "~> 0.5.0", github: "some_project/other", tag: "0.3.0" }

但它会导致一个依赖永远无法满足,因为被取出的标签总不能和需求的版本匹配.

源代码管理(scm)

Mix的设计就考虑到了支持多种的SCM工具,Hex包是默认,但:git:path是可选项.常见的一些选项是:

  • :git - 依赖是一个git版本库,Mix可以来获取和升级.
  • :path - 依赖是文件系统中的一个路径
  • :compile - 如何编译依赖
  • :app - 依赖所定义的应用的路径
  • :env - 依赖所用的环境(详情在后),默认是:prod;

每个SCM可以支持自定义选项,比如:git,支持下面的选项:

  • :ref - 用来检出git仓库的一个可选的引用(一次提交);
  • :tag - 用来检出git仓库的一个可选的tag
  • :branch - 用来检出git仓库的一个可选的分支
  • :submodules - 当为true,在依赖中递归地初始化子模块;

编译依赖

为了编译依赖,Mix会选择最适合的方式.依赖所包含的文件不同,编译的方式也不一样:

  • mix.exs
    • 直接用Mix的compile任务编译依赖;
  • rebar.configrebar.config.script
    • rebar compile deps_dir=DEPS编译,DEPS是项目依赖的安装目录;
  • Makefile
    • 简单地调用make;

如果编译的代码里没有包含以上的任何,你可以在``选项里直接指定一个命令:

{:some_dep, git: "...", compile: "./configure && make"}

如果:compile被设为false, 不做任何事情.

重复性

任何一个依赖管理工具的重要特性是可重复性.因此当你初次获取依赖,Mix将创建一个文件``,用来包含每个依赖所取出的索引.

当另一个开发者得到这个项目的拷贝,Mix将取出相同的那个索引,保证其他的开发者能“重复”同样的设置.

运行deps.update能自动升级锁,用deps.unlock任务来移除锁.

依赖任务

Elixir自带了许多用来管理项目依赖的任务:

  • mix deps - 列出所有的依赖和它的情况;
  • mix deps.get - 获取所有可得的依赖
  • mix deps.compile - 编译依赖
  • mix deps.update - 更新依赖;
  • mix deps.clean - 删除依赖文件;
  • mix deps.unlock - 解锁依赖

mix help来获取更多信息.

依赖的依赖

如果你的依赖是一个Mix或rebar的项目,Mix知道如何应付:它将自动获取和处理你的依赖的所有的依赖.然而,如果你的项目中有两个依赖共享了同一个依赖,但它们的SCM信息又无法互相匹配的话,Mix将标明这个依赖是分裂的,并发出警告.要解决而这个问题,你可以在你项目中声明选项override: true, Mix将根据这个信息来获取依赖.

伞形项目

你是否想过,如果能将几个Mix项目打包在一起,只需一个命令就可以运行各自的Mix任务, 该有多方便?.这种将项目打包在一起使用的情况被称为伞形项目.一个伞形项目可以用下面的命令来创建:

1
$ mix new project --umbrella

这将会创建一个包含以下内容的``文件:

1
defmodule Project.Mixfile do
  use Mix.Project

  def project do
    [apps_path: "apps"]
  end
end

apps_path选项指定了子项目所在的文件夹.在伞形项目中运行的Mix任务,会对apps_path文件夹中的每一个子项目起作用.例如mix compilemix test将编译或测试文件夹下的每一个项目.值得注意的是,伞形项目既不是一个普通的Mix项目,也不是一个OTP应用,也不能修改其中的源码.

如果在子项目之间有互相依赖的存在,需要指定它们的顺序,这样Mix才能正确编译.如果项目A依赖于项目B,这个依赖关系必须在项目A的mix.exs文件里指定.修改文件mix.exs来指定这个依赖:

1
defmodule A.Mixfile do
  use Mix.Project

  def project do
    [app: :a,
     deps_path: "../../deps",
     lockfile: "../../mix.lock",
     deps: deps]
  end

  defp deps do
    [{ :b, in_umbrella: true }]
  end
end

注意上面的子项目中deps_pathlockfile选项.如果在你的伞形项目的所有子项中都有它们,它们将共享依赖.apps文件夹中的mix new将自动用这些预设的选型创建一个项目.

环境

Mix有环境的概念,能让一个开发者去基于一个外部的设定来定制编译和其他的选项.默认下,Mix能理解项目三类环境:

  • dev - mix任务的默认环境;
  • test - 用于mix test;
  • prod - 在这个环境下,依赖将被载入和编译;

在默认情况下,这些环境的行为没有不同,我们之前看到的所有配置都将会影响到这三个环境.针对某个环境的定制,可以通过访问Mix.env来实现:

1
def project do
  [deps_path: deps_path(Mix.env)]
end

defp deps_path(:prod), do: "prod_deps"
defp deps_path(_), do: "deps"

Mix默认为dev环境(除了测试).可以通过修改环境变量MIX_ENV来改变环境.

1
$ MIX_ENV=prod mix compile

在下一章,我们将学习如何用Mix编写OTP应用和如何创建你自己的任务.