Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9decf4c78a | ||
|
|
e2463a5589 | ||
|
|
a825f5a0c8 | ||
|
|
3c0d26cb74 | ||
|
|
f7a31ac804 | ||
|
|
31aebcdc59 | ||
|
|
afbac81918 | ||
|
|
0b78f86f08 | ||
|
|
7180b98f47 | ||
|
|
021cca436d | ||
|
|
a3d88dc406 | ||
|
|
58a6291f17 | ||
|
|
81884247d7 | ||
|
|
c07a34f9a8 | ||
|
|
e05bfc78cb | ||
|
|
cb430702c9 | ||
|
|
6aaa4bb06d | ||
|
|
a6b80a19c5 | ||
|
|
8be0ad0499 | ||
|
|
8da4936ac3 | ||
|
|
bcc92ccc40 | ||
|
|
d64d4b009c |
@@ -85,6 +85,33 @@ ask Mix to use Rebar 3 to compile it by passing the `manager: :rebar3` option.
|
||||
Once configured, Mix will prompt you to install Rebar 3 if it is not yet
|
||||
available.
|
||||
|
||||
## v1.2.2
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
* [Kernel] Support `@compile {:autoload, false}` to disable automatic loading after compilation
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
* [ExUnit] Raise if trying to override reserved tag in `setup` blocks
|
||||
* [Mix] Ensure retrieve compile manifests do fail if some compilers are not yet available
|
||||
* [Mix] Automatically merge managers according to the mix > rebar3 > rebar > make order
|
||||
* [Mix] Force recompilation if dependency was recently fetched
|
||||
|
||||
## v1.2.1 (2016-01-14)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
* [IEx] Support remote pids/ports with IEx helper `i/1`
|
||||
* [Protocol] Warn when `defimpl` is called for a consolidated protocol
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
* [ExUnit] Ensure `assert` macros can be used from quoted code
|
||||
* [ExUnit] Do not warn in match assertion if variable is reused in pattern
|
||||
* [Macro] Fix a bug in `Macro.to_string/1` where a remote function could be accidentally interpreted as a sigil
|
||||
* [Mix] Ensure dependencies are properly skipped when `--only` option is given to `mix deps.get`
|
||||
|
||||
## v1.2.0 (2016-01-01)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
REBAR ?= "$(CURDIR)/rebar"
|
||||
PREFIX ?= /usr/local
|
||||
DOCS := master
|
||||
DOCS := v1.2
|
||||
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
|
||||
+1
-3
@@ -30,12 +30,10 @@ This document simply outlines the release process:
|
||||
|
||||
12. Create a new branch "vMAJOR.MINOR"
|
||||
|
||||
13. Move docs generation to `docs/vMAJOR.MINOR` in Makefile and copy them from `docs/stable`
|
||||
13. Move docs generation to `docs/vMAJOR.MINOR` in Makefile and copy them from `docs/stable` (change index.html accordingly)
|
||||
|
||||
14. In master, bump versions, start new CHANGELOG, add `-dev` back and commit "Start vVERSION+1"
|
||||
|
||||
15. `make release_docs` and push it to `elixir-lang/docs`
|
||||
|
||||
## Places where version is mentioned
|
||||
|
||||
* VERSION (make sure there is no newline in this file)
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
defmodule Access do
|
||||
@moduledoc """
|
||||
Dictionary-like access to data structures via the `foo[bar]` syntax.
|
||||
Key-based access to data structures via the `foo[bar]` syntax.
|
||||
|
||||
This module also empowers `Kernel`s nested update functions
|
||||
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3` and
|
||||
`Kernel.get_and_update_in/3`.
|
||||
Elixir provides two syntaxes for accessing values. `user[:name]`
|
||||
is used by dynamic structures, like maps and keywords, while
|
||||
`user.name` is used by structs. The main difference is that
|
||||
`user[:name]` won't raise if the key `:name` is missing but
|
||||
`user.name` will raise if there is no `:name` key.
|
||||
|
||||
## Examples
|
||||
## Key-based lookups
|
||||
|
||||
Out of the box, Access works with built-in dictionaries: `Keyword`
|
||||
and `Map`:
|
||||
Out of the box, Access works with `Keyword` and `Map`:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
iex> keywords[:a]
|
||||
@@ -23,13 +24,67 @@ defmodule Access do
|
||||
iex> star_ratings[1.5]
|
||||
"★☆"
|
||||
|
||||
Access can be combined with `Kernel.put_in/3` to put a value
|
||||
in a given key:
|
||||
|
||||
iex> map = %{a: 1, b: 2}
|
||||
iex> put_in map[:a], 3
|
||||
%{a: 3, b: 2}
|
||||
|
||||
This syntax is very convenient as it can be nested arbitrarily:
|
||||
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> put_in users["john"][:age], 28
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 23}}
|
||||
|
||||
Furthermore, Access transparently ignores `nil` values:
|
||||
|
||||
iex> keywords = [a: 1, b: 2]
|
||||
iex> keywords[:c][:unknown]
|
||||
nil
|
||||
|
||||
The key comparison must be implemented using the `===` operator.
|
||||
Since Access is a behaviour, it can be implemented to key-value
|
||||
data structures. Access requires the key comparison to be
|
||||
implemented using the `===` operator.
|
||||
|
||||
## Field-based lookups
|
||||
|
||||
The Access syntax (`foo[bar]`) cannot be used to access fields in
|
||||
structs. That's by design, as Access is meant to be used for
|
||||
dynamic key-value structures, like maps and keywords, and not
|
||||
by static ones like structs.
|
||||
|
||||
However Elixir already provides a field-based lookup for structs.
|
||||
Imagine a struct named `User` with name and age fields. The
|
||||
following would raise:
|
||||
|
||||
user = %User{name: "john"}
|
||||
user[:name]
|
||||
** (UndefinedFunctionError) undefined function User.fetch/2
|
||||
(User does not implement the Access behaviour)
|
||||
|
||||
Structs instead use the `user.name` syntax:
|
||||
|
||||
user.name
|
||||
#=> "john"
|
||||
|
||||
The same `user.name` syntax can also be used by `Kernel.put_in/2`
|
||||
to for updating structs fields:
|
||||
|
||||
put_in user.name, "mary"
|
||||
%User{name: "mary"}
|
||||
|
||||
Differently from `user[:name]`, `user.name` cannot be extended by
|
||||
the developers, and will be always restricted to only maps and
|
||||
structs.
|
||||
|
||||
Summing up:
|
||||
|
||||
* `user[:name]` is used by dynamic structures, is extensible and
|
||||
does not raise on missing keys
|
||||
* `user.name` is used by static structures, it is not extensible
|
||||
and it will raise on missing keys
|
||||
|
||||
"""
|
||||
|
||||
@type t :: list | map | nil
|
||||
@@ -39,6 +94,20 @@ defmodule Access do
|
||||
@callback fetch(t, key) :: {:ok, value} | :error
|
||||
@callback get_and_update(t, key, (value -> {value, value})) :: {value, t}
|
||||
|
||||
defmacrop raise_undefined_behaviour(e, struct, top) do
|
||||
quote do
|
||||
stacktrace = System.stacktrace
|
||||
e =
|
||||
case stacktrace do
|
||||
[unquote(top)|_] ->
|
||||
%{unquote(e) | reason: "#{inspect unquote(struct)} does not implement the Access behaviour"}
|
||||
_ ->
|
||||
unquote(e)
|
||||
end
|
||||
reraise e, stacktrace
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the container's value for the given key.
|
||||
"""
|
||||
@@ -47,6 +116,9 @@ defmodule Access do
|
||||
|
||||
def fetch(%{__struct__: struct} = container, key) do
|
||||
struct.fetch(container, key)
|
||||
rescue
|
||||
e in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour e, struct, {^struct, :fetch, [^container, ^key], _}
|
||||
end
|
||||
|
||||
def fetch(%{} = map, key) do
|
||||
@@ -96,6 +168,9 @@ defmodule Access do
|
||||
|
||||
def get_and_update(%{__struct__: struct} = container, key, fun) do
|
||||
struct.get_and_update(container, key, fun)
|
||||
rescue
|
||||
e in UndefinedFunctionError ->
|
||||
raise_undefined_behaviour e, struct, {^struct, :get_and_update, [^container, ^key, ^fun], _}
|
||||
end
|
||||
|
||||
def get_and_update(%{} = map, key, fun) do
|
||||
|
||||
@@ -640,6 +640,10 @@ defmodule UndefinedFunctionError do
|
||||
"undefined function " <> Exception.format_mfa(module, function, arity) <>
|
||||
" (function #{fa} is not available)"
|
||||
end
|
||||
|
||||
def message(%{reason: reason, module: module, function: function, arity: arity}) do
|
||||
"undefined function " <> Exception.format_mfa(module, function, arity) <> " (#{reason})"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule FunctionClauseError do
|
||||
|
||||
@@ -571,6 +571,10 @@ defmodule Macro do
|
||||
fun.(ast, "&" <> to_string(mod, fun) <> "." <> Atom.to_string(name) <> "/" <> to_string(arity, fun))
|
||||
end
|
||||
|
||||
def to_string({:&, _, [arg]} = ast, fun) when not is_integer(arg) do
|
||||
fun.(ast, "&(" <> to_string(arg, fun) <> ")")
|
||||
end
|
||||
|
||||
# Unary ops
|
||||
def to_string({unary, _, [{binary, _, [_, _]} = arg]} = ast, fun)
|
||||
when unary in unquote(@unary_ops) and binary in unquote(@binary_ops) do
|
||||
@@ -691,7 +695,7 @@ defmodule Macro do
|
||||
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, [])
|
||||
defp module_to_string(other, fun), do: call_to_string(other, fun)
|
||||
|
||||
defp sigil_call({func, _, [{:<<>>, _, _} = bin, args]} = ast, fun) when is_list(args) do
|
||||
defp sigil_call({func, _, [{:<<>>, _, _} = bin, args]} = ast, fun) when is_atom(func) and is_list(args) do
|
||||
sigil =
|
||||
case Atom.to_string(func) do
|
||||
<<"sigil_", name>> ->
|
||||
|
||||
@@ -540,6 +540,7 @@ defmodule Protocol do
|
||||
"the dict[key] syntax, please implement the Dict behaviour instead"
|
||||
else
|
||||
Protocol.assert_protocol!(protocol)
|
||||
Protocol.__ensure_defimpl__(protocol, for, __ENV__)
|
||||
end
|
||||
|
||||
defmodule name do
|
||||
@@ -580,6 +581,7 @@ defmodule Protocol do
|
||||
defp derive(protocol, for, struct, opts, env) do
|
||||
extra = ", cannot derive #{inspect protocol} for #{inspect for}"
|
||||
assert_protocol!(protocol, extra)
|
||||
__ensure_defimpl__(protocol, for, env)
|
||||
assert_impl!(protocol, Any, extra)
|
||||
|
||||
# Clean up variables from eval context
|
||||
@@ -608,6 +610,17 @@ defmodule Protocol do
|
||||
end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __ensure_defimpl__(protocol, for, env) do
|
||||
if Protocol.consolidated?(protocol) do
|
||||
message =
|
||||
"the #{inspect protocol} protocol has already been consolidated" <>
|
||||
", an implementation for #{inspect for} has no effect"
|
||||
:elixir_errors.warn(env.line, env.file, message)
|
||||
end
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __spec__?(module, name, arity) do
|
||||
signature = {name, arity}
|
||||
|
||||
@@ -8,10 +8,10 @@ defmodule String do
|
||||
|
||||
The functions in this module act according to the Unicode
|
||||
Standard, version 6.3.0.
|
||||
|
||||
|
||||
As per the standard, a codepoint is a single Unicode Character,
|
||||
which may be represented by one or more bytes.
|
||||
|
||||
|
||||
For example, the codepoint "é" is two bytes:
|
||||
|
||||
iex> byte_size("é")
|
||||
|
||||
@@ -78,9 +78,8 @@ code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
|
||||
|
||||
%% Pass {native, false} to speed up bootstrap
|
||||
%% process when native is set to true
|
||||
AllOpts = options(),
|
||||
FinalOpts = AllOpts -- [native, warn_missing_spec],
|
||||
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
|
||||
ErlOpts = options() -- [native, warn_missing_spec],
|
||||
inner_module(Form, ErlOpts, [{bootstrap, true}], E, fun(_, Binary) ->
|
||||
%% If we have labeled locals, anonymous functions
|
||||
%% were created and therefore we cannot ditch the
|
||||
%% module
|
||||
@@ -169,22 +168,32 @@ allows_fast_compilation(_) -> false.
|
||||
%% executes the callback in case of success. This automatically
|
||||
%% handles errors and warnings. Used by this module and elixir_module.
|
||||
module(Forms, Opts, E, Callback) ->
|
||||
Final =
|
||||
case (get_opt(debug_info) == true) orelse
|
||||
lists:member(debug_info, Opts) of
|
||||
true -> [debug_info] ++ options();
|
||||
false -> options()
|
||||
ErlOpts =
|
||||
case proplists:get_value(debug_info, Opts) of
|
||||
true -> [debug_info];
|
||||
false -> [];
|
||||
undefined ->
|
||||
case get_opt(debug_info) of
|
||||
true -> [debug_info];
|
||||
false -> []
|
||||
end
|
||||
end,
|
||||
inner_module(Forms, Final, false, E, Callback).
|
||||
inner_module(Forms, ErlOpts ++ options(), Opts, E, Callback).
|
||||
|
||||
inner_module(Forms, Options, Bootstrap, #{file := File} = E, Callback) when
|
||||
is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
|
||||
inner_module(Forms, ErlOpts, ExOpts, #{file := File} = E, Callback) when
|
||||
is_list(Forms), is_list(ErlOpts), is_list(ExOpts), is_function(Callback) ->
|
||||
Source = elixir_utils:characters_to_list(File),
|
||||
Autoload = proplists:get_value(autoload, ExOpts, true),
|
||||
Bootstrap = proplists:get_value(bootstrap, ExOpts, false),
|
||||
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return, {source, Source}|Options]) of
|
||||
case compile:noenv_forms([no_auto_import()|Forms], [return, {source, Source}|ErlOpts]) of
|
||||
{ok, Module, Binary, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
{module, Module} = code:load_binary(Module, beam_location(E), Binary),
|
||||
{module, Module} =
|
||||
case Autoload of
|
||||
true -> code:load_binary(Module, beam_location(E), Binary);
|
||||
false -> {module, Module}
|
||||
end,
|
||||
Callback(Module, Binary);
|
||||
{error, Errors, Warnings} ->
|
||||
format_warnings(Bootstrap, Warnings),
|
||||
|
||||
@@ -331,7 +331,7 @@ spec_for_macro(Else) -> Else.
|
||||
|
||||
compile_opts(Module) ->
|
||||
case ets:lookup(data_table(Module), compile) of
|
||||
[{compile, Opts}] when is_list(Opts) -> Opts;
|
||||
[{compile, Opts}] when is_list(Opts) -> lists:flatten(Opts);
|
||||
[] -> []
|
||||
end.
|
||||
|
||||
|
||||
@@ -59,4 +59,20 @@ defmodule AccessTest do
|
||||
assert Access.get_and_update(%{}, :foo, fn nil -> {:ok, :baz} end) == {:ok, %{foo: :baz}}
|
||||
assert Access.get_and_update(%{foo: :bar}, :foo, fn :bar -> {:ok, :baz} end) == {:ok, %{foo: :baz}}
|
||||
end
|
||||
|
||||
test "for struct" do
|
||||
defmodule Sample do
|
||||
defstruct [:name]
|
||||
end
|
||||
|
||||
assert_raise UndefinedFunctionError,
|
||||
"undefined function AccessTest.Sample.fetch/2 (AccessTest.Sample does not implement the Access behaviour)", fn ->
|
||||
Access.fetch(struct(Sample, []), :name)
|
||||
end
|
||||
|
||||
assert_raise UndefinedFunctionError,
|
||||
"undefined function AccessTest.Sample.get_and_update/3 (AccessTest.Sample does not implement the Access behaviour)", fn ->
|
||||
Access.get_and_update(struct(Sample, []), :name, fn nil -> {:ok, :baz} end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -290,6 +290,8 @@ defmodule MacroTest do
|
||||
assert Macro.to_string(quote do: ~R"123") == ~s/~R"123"/
|
||||
assert Macro.to_string(quote do: ~R"123"u) == ~s/~R"123"u/
|
||||
assert Macro.to_string(quote do: ~R"\n123") == ~s/~R"\\\\n123"/
|
||||
|
||||
assert Macro.to_string(quote do: Foo.bar(<<>>, [])) == "Foo.bar(<<>>, [])"
|
||||
end
|
||||
|
||||
test "arrow to string" do
|
||||
@@ -387,6 +389,7 @@ defmodule MacroTest do
|
||||
assert Macro.to_string(quote do: &foo/0) == "&foo/0"
|
||||
assert Macro.to_string(quote do: &Foo.foo/0) == "&Foo.foo/0"
|
||||
assert Macro.to_string(quote do: & &1 + &2) == "&(&1 + &2)"
|
||||
assert Macro.to_string(quote do: & &1) == "&(&1)"
|
||||
end
|
||||
|
||||
test "containers to string" do
|
||||
@@ -422,7 +425,6 @@ defmodule MacroTest do
|
||||
assert Macro.to_string(quote do: !(foo > bar)) == "!(foo > bar)"
|
||||
assert Macro.to_string(quote do: @foo(bar)) == "@foo(bar)"
|
||||
assert Macro.to_string(quote do: identity(&1)) == "identity(&1)"
|
||||
assert Macro.to_string(quote do: identity(&foo)) == "identity(&foo)"
|
||||
end
|
||||
|
||||
test "access to string" do
|
||||
|
||||
@@ -144,6 +144,13 @@ defmodule ModuleTest do
|
||||
assert [3, 2, 1] = @other_attribute
|
||||
end
|
||||
|
||||
test "@compile autoload attribute" do
|
||||
defmodule NoAutoload do
|
||||
@compile {:autoload, false}
|
||||
end
|
||||
refute :code.is_loaded(NoAutoload)
|
||||
end
|
||||
|
||||
## Naming
|
||||
|
||||
test "concat" do
|
||||
|
||||
@@ -319,6 +319,17 @@ defmodule Protocol.ConsolidationTest do
|
||||
refute Protocol.consolidated?(Enumerable)
|
||||
end
|
||||
|
||||
test "consolidation prevents new implementations" do
|
||||
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||
defimpl WithAny, for: Integer do
|
||||
def ok(_any), do: :ok
|
||||
end
|
||||
end) =~ ~r"warning: the .+WithAny protocol has already been consolidated"
|
||||
after
|
||||
:code.purge(WithAny.Atom)
|
||||
:code.delete(WithAny.Atom)
|
||||
end
|
||||
|
||||
test "consolidated implementations without any" do
|
||||
assert is_nil Sample.impl_for(:foo)
|
||||
assert is_nil Sample.impl_for(fn(x) -> x end)
|
||||
|
||||
@@ -539,4 +539,10 @@ defmodule StringTest do
|
||||
assert String.jaro_distance("jon", "john") == 0.9166666666666666
|
||||
assert String.jaro_distance("jon", "jan") == 0.7777777777777777
|
||||
end
|
||||
|
||||
test "skip debug_info in metadata modules" do
|
||||
assert String.Unicode.__info__(:compile)[:options] == []
|
||||
assert String.Graphemes.__info__(:compile)[:options] == []
|
||||
assert String.Normalizer.__info__(:compile)[:options] == []
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,6 +10,7 @@ end
|
||||
|
||||
defmodule String.Unicode do
|
||||
@moduledoc false
|
||||
@compile {:debug_info, false}
|
||||
def version, do: {7, 0, 0}
|
||||
|
||||
data_path = Path.join(__DIR__, "UnicodeData.txt")
|
||||
@@ -204,6 +205,7 @@ end
|
||||
|
||||
defmodule String.Graphemes do
|
||||
@moduledoc false
|
||||
@compile {:debug_info, false}
|
||||
|
||||
cluster_path = Path.join(__DIR__, "GraphemeBreakProperty.txt")
|
||||
regex = ~r/(?:^([0-9A-F]+)(?:\.\.([0-9A-F]+))?)\s+;\s(\w+)/m
|
||||
@@ -413,6 +415,7 @@ end
|
||||
|
||||
defmodule String.Normalizer do
|
||||
@moduledoc false
|
||||
@compile {:debug_info, false}
|
||||
|
||||
decomposition_path = Path.join(__DIR__, "Decomposition.txt")
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ defmodule ExUnit do
|
||||
|
||||
@typedoc "The error state returned by ExUnit.Test and ExUnit.TestCase"
|
||||
@type state :: nil | {:failed, failed} | {:skip, binary} | {:invalid, module}
|
||||
@type failed :: {Exception.kind, reason :: term, stacktrace :: [tuple]}
|
||||
@type failed :: [{Exception.kind, reason :: term, stacktrace :: [tuple]}]
|
||||
|
||||
defmodule Test do
|
||||
@moduledoc """
|
||||
|
||||
@@ -102,15 +102,15 @@ defmodule ExUnit.Assertions do
|
||||
# is not nil nor false. We need to rewrite the if
|
||||
# to avoid silly warnings though.
|
||||
return =
|
||||
no_warning(quote do
|
||||
if right do
|
||||
right
|
||||
else
|
||||
raise ExUnit.AssertionError,
|
||||
expr: expr,
|
||||
message: "Expected truthy, got #{inspect right}"
|
||||
end
|
||||
end)
|
||||
no_warning(quote do
|
||||
if right do
|
||||
right
|
||||
else
|
||||
raise ExUnit.AssertionError,
|
||||
expr: expr,
|
||||
message: "Expected truthy, got #{inspect right}"
|
||||
end
|
||||
end)
|
||||
|
||||
quote do
|
||||
right = unquote(right)
|
||||
@@ -401,20 +401,19 @@ defmodule ExUnit.Assertions do
|
||||
end
|
||||
|
||||
defp collect_vars_from_pattern(expr) do
|
||||
{_, vars} =
|
||||
Macro.prewalk(expr, [], fn
|
||||
{:::, _, [left, _]}, acc ->
|
||||
{[left], acc}
|
||||
{skip, _, [_]}, acc when skip in [:^, :@] ->
|
||||
{:ok, acc}
|
||||
{:_, _, context}, acc when is_atom(context) ->
|
||||
{:ok, acc}
|
||||
{name, _, context}, acc when is_atom(name) and is_atom(context) ->
|
||||
{:ok, [{name, [generated: true], context}|acc]}
|
||||
node, acc ->
|
||||
{node, acc}
|
||||
end)
|
||||
Enum.uniq(vars)
|
||||
Macro.prewalk(expr, [], fn
|
||||
{:::, _, [left, _]}, acc ->
|
||||
{[left], acc}
|
||||
{skip, _, [_]}, acc when skip in [:^, :@] ->
|
||||
{:ok, acc}
|
||||
{:_, _, context}, acc when is_atom(context) ->
|
||||
{:ok, acc}
|
||||
{name, meta, context}, acc when is_atom(name) and is_atom(context) ->
|
||||
{:ok, [{name, [generated: true] ++ meta, context} | acc]}
|
||||
node, acc ->
|
||||
{node, acc}
|
||||
end)
|
||||
|> elem(1)
|
||||
end
|
||||
|
||||
defp no_warning({name, meta, args}) do
|
||||
|
||||
@@ -146,6 +146,8 @@ defmodule ExUnit.Callbacks do
|
||||
|
||||
## Helpers
|
||||
|
||||
@reserved ~w(case test line file)a
|
||||
|
||||
@doc false
|
||||
def __merge__(_mod, context, :ok) do
|
||||
{:ok, context}
|
||||
@@ -163,12 +165,16 @@ defmodule ExUnit.Callbacks do
|
||||
raise_merge_failed!(mod, data)
|
||||
end
|
||||
|
||||
defp context_merge(_mod, context, %{} = data) do
|
||||
Map.merge(context, data)
|
||||
defp context_merge(mod, context, %{} = data) do
|
||||
Map.merge(context, data, fn
|
||||
_, v, v -> v
|
||||
k, _, v when k in @reserved -> raise_merge_reserved!(mod, k, v)
|
||||
_, _, v -> v
|
||||
end)
|
||||
end
|
||||
|
||||
defp context_merge(_mod, context, data) when is_list(data) do
|
||||
Enum.into(data, context)
|
||||
defp context_merge(mod, context, data) when is_list(data) do
|
||||
context_merge(mod, context, Map.new(data))
|
||||
end
|
||||
|
||||
defp context_merge(mod, _context, data) do
|
||||
@@ -177,7 +183,12 @@ defmodule ExUnit.Callbacks do
|
||||
|
||||
defp raise_merge_failed!(mod, data) do
|
||||
raise "expected ExUnit callback in #{inspect mod} to return :ok " <>
|
||||
" or {:ok, keyword | map}, got #{inspect data} instead"
|
||||
"or {:ok, keyword | map}, got #{inspect data} instead"
|
||||
end
|
||||
|
||||
defp raise_merge_reserved!(mod, key, value) do
|
||||
raise "expected ExUnit callback in #{inspect mod} is trying to set " <>
|
||||
"reserved field #{inspect key} to #{inspect value}"
|
||||
end
|
||||
|
||||
defp escape(contents) do
|
||||
|
||||
@@ -114,10 +114,11 @@ defmodule ExUnit.Case do
|
||||
The following tags are set automatically by ExUnit and are
|
||||
therefore reserved:
|
||||
|
||||
* `:case` - the test case module
|
||||
* `:test` - the test name
|
||||
* `:line` - the line on which the test was defined
|
||||
* `:file` - the file on which the test was defined
|
||||
* `:case` - the test case module
|
||||
* `:test` - the test name
|
||||
* `:line` - the line on which the test was defined
|
||||
* `:file` - the file on which the test was defined
|
||||
* `:async` - if the test case is in async mode
|
||||
|
||||
The following tags customize how tests behaves:
|
||||
|
||||
@@ -174,7 +175,7 @@ defmodule ExUnit.Case do
|
||||
|
||||
This default can be overriden by `@tag capture_log: false` or `@moduletag capture_log: false`.
|
||||
|
||||
Since `setup_all` blocks don't belong to a specific test, log messages generated in them (or
|
||||
Since `setup_all` blocks don't belong to a specific test, log messages generated in them (or
|
||||
between tests) are never captured. If you want to suppress these messages as well, remove the
|
||||
console backend globally:
|
||||
|
||||
@@ -193,20 +194,16 @@ defmodule ExUnit.Case do
|
||||
end
|
||||
|
||||
quote do
|
||||
async = !!unquote(async)
|
||||
|
||||
unless Module.get_attribute(__MODULE__, :ex_unit_tests) do
|
||||
Enum.each [:ex_unit_tests, :tag, :moduletag],
|
||||
&Module.register_attribute(__MODULE__, &1, accumulate: true)
|
||||
|
||||
if unquote(async) do
|
||||
@moduletag async: true
|
||||
ExUnit.Server.add_async_case(__MODULE__)
|
||||
else
|
||||
@moduletag async: false
|
||||
ExUnit.Server.add_sync_case(__MODULE__)
|
||||
end
|
||||
|
||||
@moduletag async: async
|
||||
@before_compile ExUnit.Case
|
||||
@ex_unit_test_names %{}
|
||||
@ex_unit_async async
|
||||
use ExUnit.Callbacks
|
||||
end
|
||||
|
||||
@@ -254,7 +251,7 @@ defmodule ExUnit.Case do
|
||||
|
||||
quote bind_quoted: binding do
|
||||
test = :"test #{message}"
|
||||
ExUnit.Case.__on_definition__(__ENV__, test)
|
||||
ExUnit.Case.__on_definition__(__ENV__, test, [])
|
||||
def unquote(test)(unquote(var)), do: unquote(contents)
|
||||
end
|
||||
end
|
||||
@@ -284,6 +281,12 @@ defmodule ExUnit.Case do
|
||||
@doc false
|
||||
defmacro __before_compile__(_) do
|
||||
quote do
|
||||
if @ex_unit_async do
|
||||
ExUnit.Server.add_async_case(__MODULE__)
|
||||
else
|
||||
ExUnit.Server.add_sync_case(__MODULE__)
|
||||
end
|
||||
|
||||
def __ex_unit__(:case) do
|
||||
%ExUnit.TestCase{name: __MODULE__, tests: @ex_unit_tests}
|
||||
end
|
||||
@@ -291,8 +294,7 @@ defmodule ExUnit.Case do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __on_definition__(env, name, tags \\ []) do
|
||||
mod = env.module
|
||||
def __on_definition__(%{module: mod, file: file, line: line}, name, tags) do
|
||||
moduletag = Module.get_attribute(mod, :moduletag)
|
||||
|
||||
unless moduletag do
|
||||
@@ -304,7 +306,7 @@ defmodule ExUnit.Case do
|
||||
(tags ++ Module.get_attribute(mod, :tag) ++ moduletag)
|
||||
|> normalize_tags
|
||||
|> validate_tags
|
||||
|> Map.merge(%{line: env.line, file: env.file})
|
||||
|> Map.merge(%{line: line, file: file})
|
||||
|
||||
test = %ExUnit.Test{name: name, case: mod, tags: tags}
|
||||
test_names = Module.get_attribute(mod, :ex_unit_test_names)
|
||||
|
||||
@@ -11,6 +11,16 @@ alias ExUnit.AssertionsTest.Value
|
||||
defmodule ExUnit.AssertionsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
defmacrop assert_ok(arg) do
|
||||
quote do
|
||||
assert {:ok, val} = ok(unquote(arg))
|
||||
end
|
||||
end
|
||||
|
||||
test "assert inside macro" do
|
||||
assert_ok 42
|
||||
end
|
||||
|
||||
test "assert with true value" do
|
||||
true = assert Value.truthy
|
||||
end
|
||||
|
||||
@@ -152,7 +152,7 @@ defmodule ExUnitTest do
|
||||
refute output =~ "[debug] four"
|
||||
end
|
||||
|
||||
test "it supports multi errors" do
|
||||
test "supports multi errors" do
|
||||
capture_io :stderr, fn ->
|
||||
defmodule MultiTest do
|
||||
use ExUnit.Case
|
||||
@@ -187,7 +187,7 @@ defmodule ExUnitTest do
|
||||
assert output =~ "Failure #2"
|
||||
end
|
||||
|
||||
test "it registers only the first test with any given name" do
|
||||
test "registers only the first test with any given name" do
|
||||
capture_io :stderr, fn ->
|
||||
defmodule TestWithSameNames do
|
||||
use ExUnit.Case
|
||||
@@ -207,7 +207,7 @@ defmodule ExUnitTest do
|
||||
end) =~ "1 test, 0 failure"
|
||||
end
|
||||
|
||||
test "it produces error on not implemented tests" do
|
||||
test "produces error on not implemented tests" do
|
||||
defmodule TestNotImplemented do
|
||||
use ExUnit.Case
|
||||
|
||||
@@ -227,7 +227,7 @@ defmodule ExUnitTest do
|
||||
assert output =~ "1 test, 1 failure"
|
||||
end
|
||||
|
||||
test "it skips tagged test with skip" do
|
||||
test "skips tagged test with skip" do
|
||||
defmodule TestSkipped do
|
||||
use ExUnit.Case
|
||||
|
||||
@@ -250,6 +250,55 @@ defmodule ExUnitTest do
|
||||
assert output =~ "2 tests, 0 failures, 2 skipped"
|
||||
end
|
||||
|
||||
test "raises on reserved tag in module" do
|
||||
assert_raise RuntimeError, "cannot set tag :file because it is reserved by ExUnit", fn ->
|
||||
defmodule ReservedTag do
|
||||
use ExUnit.Case
|
||||
|
||||
setup do
|
||||
{:ok, file: :foo}
|
||||
end
|
||||
|
||||
@tag file: "oops"
|
||||
test "sample", do: :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "raises on reserved tag in setup" do
|
||||
defmodule ReservedSetupTag do
|
||||
use ExUnit.Case
|
||||
|
||||
setup do
|
||||
{:ok, file: :foo}
|
||||
end
|
||||
|
||||
test "sample", do: :ok
|
||||
end
|
||||
|
||||
output = capture_io(fn ->
|
||||
assert ExUnit.run == %{failures: 1, skipped: 0, total: 1}
|
||||
end)
|
||||
|
||||
assert output =~ "trying to set reserved field :file"
|
||||
end
|
||||
|
||||
test "does not raise on reserved tag in setup_all (lower priority)" do
|
||||
defmodule ReservedSetupAllTag do
|
||||
use ExUnit.Case
|
||||
|
||||
setup_all do
|
||||
{:ok, file: :foo}
|
||||
end
|
||||
|
||||
test "sample", do: :ok
|
||||
end
|
||||
|
||||
capture_io(fn ->
|
||||
assert ExUnit.run == %{failures: 0, skipped: 0, total: 1}
|
||||
end)
|
||||
end
|
||||
|
||||
defp run_with_filter(filters, {async, sync, load_us}) do
|
||||
opts = Keyword.merge(ExUnit.configuration, filters)
|
||||
output = capture_io fn ->
|
||||
|
||||
@@ -63,6 +63,7 @@ defmodule IEx.Config do
|
||||
defp default_option(:colors), do: [{:enabled, IO.ANSI.enabled?} | default_colors()]
|
||||
defp default_option(:inspect), do: []
|
||||
defp default_option(:history_size), do: 20
|
||||
defp default_option(:width), do: width()
|
||||
|
||||
defp default_option(prompt) when prompt in [:default_prompt, :alive_prompt] do
|
||||
"%prefix(%counter)>"
|
||||
|
||||
+11
-9
@@ -227,13 +227,14 @@ defimpl IEx.Info, for: PID do
|
||||
|
||||
def info(pid) do
|
||||
extra =
|
||||
if info = Process.info(pid, @keys) do
|
||||
["Alive": true,
|
||||
"Name": process_name(info[:registered_name]),
|
||||
"Links": links(info[:links]),
|
||||
"Message queue length": info[:message_queue_len]]
|
||||
else
|
||||
["Alive": false]
|
||||
case :rpc.pinfo(pid, @keys) do
|
||||
[_|_] = info ->
|
||||
["Alive": true,
|
||||
"Name": process_name(info[:registered_name]),
|
||||
"Links": links(info[:links]),
|
||||
"Message queue length": info[:message_queue_len]]
|
||||
_ ->
|
||||
["Alive": false]
|
||||
end
|
||||
|
||||
["Data type": "PID"] ++ extra ++
|
||||
@@ -257,9 +258,10 @@ end
|
||||
|
||||
defimpl IEx.Info, for: Port do
|
||||
def info(port) do
|
||||
port_info = Port.info(port)
|
||||
connected = :rpc.call(node(port), :erlang, :port_info, [port, :connected])
|
||||
|
||||
["Data type": "Port",
|
||||
"Open": port_info != nil,
|
||||
"Open": match?({:connected, _}, connected),
|
||||
"Reference modules": "Port"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
Code.require_file "../test_helper.exs", __DIR__
|
||||
|
||||
defmodule IEx.ConfigTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
test "configuration sets a default for width" do
|
||||
assert IEx.Config.configuration[:width] == 80
|
||||
end
|
||||
end
|
||||
@@ -137,29 +137,26 @@ defmodule Mix.Compilers.Elixir do
|
||||
|> List.delete(module)
|
||||
|> Enum.reject(&match?("elixir_" <> _, Atom.to_string(&1)))
|
||||
|
||||
attributes = module.__info__(:attributes)
|
||||
|
||||
kind = detect_kind(attributes)
|
||||
kind = detect_kind(module)
|
||||
source = Path.relative_to(source, cwd)
|
||||
files = get_external_resources(attributes, cwd)
|
||||
files = get_external_resources(module, cwd)
|
||||
tuple = {beam, module, kind, source, compile, runtime, files, binary}
|
||||
Agent.cast pid, &:lists.keystore(beam, 1, &1, tuple)
|
||||
end
|
||||
|
||||
defp detect_kind(attributes) do
|
||||
defp detect_kind(module) do
|
||||
cond do
|
||||
impl = attributes[:impl] ->
|
||||
impl = Module.get_attribute(module, :impl) ->
|
||||
{:impl, impl[:protocol]}
|
||||
attributes[:protocol] ->
|
||||
Module.get_attribute(module, :protocol) ->
|
||||
:protocol
|
||||
true ->
|
||||
:module
|
||||
end
|
||||
end
|
||||
|
||||
defp get_external_resources(attributes, cwd) do
|
||||
for {:external_resource, values} <- attributes,
|
||||
file <- values,
|
||||
defp get_external_resources(module, cwd) do
|
||||
for file <- Module.get_attribute(module, :external_resource),
|
||||
File.regular?(file),
|
||||
relative = Path.relative_to(file, cwd),
|
||||
Path.type(relative) == :relative,
|
||||
|
||||
@@ -49,16 +49,12 @@ defmodule Mix.Dep.Converger do
|
||||
end
|
||||
|
||||
defp all(acc, lock, opts, callback) do
|
||||
deps = Mix.Dep.Loader.children()
|
||||
deps = Enum.map(deps, &(%{&1 | top_level: true}))
|
||||
lock_given? = !!lock
|
||||
main = Mix.Dep.Loader.children()
|
||||
main = Enum.map(main, &(%{&1 | top_level: true}))
|
||||
apps = Enum.map(main, &(&1.app))
|
||||
|
||||
# Filter the dependencies per environment. We pass the ones
|
||||
# left out as accumulator and upper breadth to help catch
|
||||
# inconsistencies across environment, specially regarding
|
||||
# the :only option. They are filtered again later.
|
||||
current = Enum.map(deps, &(&1.app))
|
||||
{main, only} = Mix.Dep.Loader.partition_by_env(deps, opts)
|
||||
lock_given? = !!lock
|
||||
env = opts[:env]
|
||||
|
||||
# If no lock was given, let's read one to fill in the deps
|
||||
lock = lock || Mix.Dep.Lock.read
|
||||
@@ -68,7 +64,7 @@ defmodule Mix.Dep.Converger do
|
||||
# lazily loaded, we need to check for it on every
|
||||
# iteration.
|
||||
{deps, rest, lock} =
|
||||
all(main, only, [], current, callback, acc, lock, fn dep ->
|
||||
all(main, apps, callback, acc, lock, env, fn dep ->
|
||||
if (remote = Mix.RemoteConverger.get) && remote.remote?(dep) do
|
||||
{:loaded, dep}
|
||||
else
|
||||
@@ -76,9 +72,6 @@ defmodule Mix.Dep.Converger do
|
||||
end
|
||||
end)
|
||||
|
||||
# Filter deps per environment once more. If the filtered
|
||||
# dependencies had no conflicts, they are removed now.
|
||||
{deps, _} = Mix.Dep.Loader.partition_by_env(deps, opts)
|
||||
diverged? = Enum.any?(deps, &Mix.Dep.diverged?/1)
|
||||
|
||||
# Run remote converger if one is available and rerun Mix's
|
||||
@@ -99,12 +92,11 @@ defmodule Mix.Dep.Converger do
|
||||
# In case no lock was given, we will use the local lock
|
||||
# which is potentially stale. So remote.deps/2 needs to always
|
||||
# check if the data it finds in the lock is actually valid.
|
||||
all(main, [], [], Enum.map(main, &(&1.app)), callback, rest, lock, fn dep ->
|
||||
cond do
|
||||
cached = deps[dep.app] ->
|
||||
{:loaded, cached}
|
||||
true ->
|
||||
{:unloaded, dep, remote.deps(dep, lock)}
|
||||
all(main, apps, callback, rest, lock, env, fn dep ->
|
||||
if cached = deps[dep.app] do
|
||||
{:loaded, cached}
|
||||
else
|
||||
{:unloaded, dep, remote.deps(dep, lock)}
|
||||
end
|
||||
end)
|
||||
else
|
||||
@@ -112,6 +104,14 @@ defmodule Mix.Dep.Converger do
|
||||
end
|
||||
end
|
||||
|
||||
defp all(main, apps, callback, rest, lock, env, cache) do
|
||||
{deps, rest, lock} = all(main, [], [], apps, callback, rest, lock, env, cache)
|
||||
# When traversing dependencies, we keep skipped ones to
|
||||
# find conflicts. We remove them now after traversal.
|
||||
{deps, _} = Mix.Dep.Loader.partition_by_env(deps, env)
|
||||
{deps, rest, lock}
|
||||
end
|
||||
|
||||
# We traverse the tree of dependencies in a breadth-first
|
||||
# fashion. The reason for this is that we converge
|
||||
# dependencies, but allow the parent to override any
|
||||
@@ -151,10 +151,14 @@ defmodule Mix.Dep.Converger do
|
||||
# Now, since "d" was specified in a parent project, no
|
||||
# exception is going to be raised since d is considered
|
||||
# to be the authoritative source.
|
||||
defp all([dep|t], acc, upper_breadths, current_breadths, callback, rest, lock, cache) do
|
||||
defp all([dep|t], acc, upper_breadths, current_breadths, callback, rest, lock, env, cache) do
|
||||
cond do
|
||||
new_acc = diverged_deps(acc, upper_breadths, dep) ->
|
||||
all(t, new_acc, upper_breadths, current_breadths, callback, rest, lock, cache)
|
||||
all(t, new_acc, upper_breadths, current_breadths, callback, rest, lock, env, cache)
|
||||
Mix.Dep.Loader.skip?(dep, env) ->
|
||||
# We still keep skipped dependencies around to detect conflicts.
|
||||
# They must be rejected after every all iteration.
|
||||
all(t, [dep|acc], upper_breadths, current_breadths, callback, rest, lock, env, cache)
|
||||
true ->
|
||||
dep =
|
||||
case cache.(dep) do
|
||||
@@ -170,12 +174,15 @@ defmodule Mix.Dep.Converger do
|
||||
end
|
||||
|
||||
dep = %{dep | deps: reject_non_fullfilled_optional(dep.deps, current_breadths)}
|
||||
{acc, rest, lock} = all(t, [dep|acc], upper_breadths, current_breadths, callback, rest, lock, cache)
|
||||
all(dep.deps, acc, current_breadths, Enum.map(dep.deps, &(&1.app)) ++ current_breadths, callback, rest, lock, cache)
|
||||
{acc, rest, lock} =
|
||||
all(t, [dep|acc], upper_breadths, current_breadths, callback, rest, lock, env, cache)
|
||||
|
||||
new_breadths = Enum.map(dep.deps, &(&1.app)) ++ current_breadths
|
||||
all(dep.deps, acc, current_breadths, new_breadths, callback, rest, lock, env, cache)
|
||||
end
|
||||
end
|
||||
|
||||
defp all([], acc, _upper, _current, _callback, rest, lock, _cache) do
|
||||
defp all([], acc, _upper, _current, _callback, rest, lock, _env, _cache) do
|
||||
{acc, rest, lock}
|
||||
end
|
||||
|
||||
@@ -206,7 +213,8 @@ defmodule Mix.Dep.Converger do
|
||||
in_upper? && other_opts[:override] ->
|
||||
{other |> with_matching_only(dep, in_upper?), true}
|
||||
converge?(other, dep) ->
|
||||
{other |> with_matching_only(dep, in_upper?) |> with_matching_req(dep) |> merge_manager(dep), true}
|
||||
{other |> with_matching_only(dep, in_upper?)
|
||||
|> with_matching_req(dep) |> merge_manager(dep, in_upper?), true}
|
||||
true ->
|
||||
tag = if in_upper?, do: :overridden, else: :diverged
|
||||
{%{other | status: {tag, dep}}, true}
|
||||
@@ -269,19 +277,10 @@ defmodule Mix.Dep.Converger do
|
||||
end
|
||||
end
|
||||
|
||||
defp converge?(%Mix.Dep{scm: scm1, manager: manager1, opts: opts1},
|
||||
%Mix.Dep{scm: scm2, manager: manager2, opts: opts2}) do
|
||||
scm1 == scm2 and
|
||||
manager_equal?(manager1, manager2) and
|
||||
opts_equal?(opts1, opts2) and
|
||||
scm1.equal?(opts1, opts2)
|
||||
defp converge?(%Mix.Dep{scm: scm1, opts: opts1}, %Mix.Dep{scm: scm2, opts: opts2}) do
|
||||
scm1 == scm2 and opts_equal?(opts1, opts2) and scm1.equal?(opts1, opts2)
|
||||
end
|
||||
|
||||
defp manager_equal?(manager, manager), do: true
|
||||
defp manager_equal?(_, nil), do: true
|
||||
defp manager_equal?(nil, _), do: true
|
||||
defp manager_equal?(_, _), do: false
|
||||
|
||||
defp opts_equal?(opts1, opts2) do
|
||||
keys = ~w(app env compile)a
|
||||
Enum.all?(keys, &(Keyword.fetch(opts1, &1) == Keyword.fetch(opts2, &1)))
|
||||
@@ -293,8 +292,18 @@ defmodule Mix.Dep.Converger do
|
||||
end
|
||||
end
|
||||
|
||||
defp merge_manager(other, dep) do
|
||||
%{other | manager: other.manager || dep.manager}
|
||||
defp merge_manager(%{manager: other_manager} = other, %{manager: manager}, in_upper?) do
|
||||
%{other | manager: sort_manager(other_manager, manager, in_upper?)}
|
||||
end
|
||||
|
||||
@managers [:mix, :rebar3, :rebar, :make]
|
||||
|
||||
defp sort_manager(other_manager, manager, true) do
|
||||
other_manager || manager
|
||||
end
|
||||
defp sort_manager(other_manager, manager, false) do
|
||||
priority = @managers -- (@managers -- (List.wrap(other_manager) ++ List.wrap(manager)))
|
||||
List.first(priority) || other_manager || manager
|
||||
end
|
||||
|
||||
defp with_matching_req(%Mix.Dep{} = other, %Mix.Dep{} = dep) do
|
||||
|
||||
@@ -96,9 +96,7 @@ defmodule Mix.Dep.Fetcher do
|
||||
# it for compilation too, this is our best to try to solve the
|
||||
# maximum we can at each deps.get and deps.update.
|
||||
if Enum.all?(all_deps, &available?/1) do
|
||||
deps = (with_depending(deps, all_deps) ++
|
||||
Enum.filter(all_deps, fn dep -> not ok?(dep) end))
|
||||
|> Enum.uniq(&(&1.app))
|
||||
deps = Enum.uniq_by(with_depending(deps, all_deps), &(&1.app))
|
||||
end
|
||||
|
||||
# Merge the new lock on top of the old to guarantee we don't
|
||||
|
||||
@@ -21,18 +21,18 @@ defmodule Mix.Dep.Loader do
|
||||
@doc """
|
||||
Partitions loaded dependencies by environment.
|
||||
"""
|
||||
def partition_by_env(deps, opts) do
|
||||
if env = opts[:env] do
|
||||
Enum.partition(deps, fn
|
||||
%Mix.Dep{status: {:divergedonly, _}} ->
|
||||
true
|
||||
%Mix.Dep{opts: opts} ->
|
||||
only = opts[:only] |> List.wrap |> validate_only!
|
||||
only == [] or env in List.wrap(only)
|
||||
end)
|
||||
else
|
||||
{deps, []}
|
||||
end
|
||||
def partition_by_env(deps, nil), do: {deps, []}
|
||||
def partition_by_env(deps, env), do: Enum.partition(deps, ¬ skip?(&1, env))
|
||||
|
||||
@doc """
|
||||
Check if a dependency must be skipped according to the environment.
|
||||
"""
|
||||
def skip?(_dep, nil), do: false
|
||||
def skip?(%Mix.Dep{status: {:divergedonly, _}}, _), do: false
|
||||
def skip?(%Mix.Dep{opts: opts}, env) do
|
||||
only = opts[:only]
|
||||
validate_only!(only)
|
||||
only != nil and not env in List.wrap(only)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -283,7 +283,7 @@ defmodule Mix.Dep.Loader do
|
||||
end
|
||||
|
||||
defp validate_only!(only) do
|
||||
for entry <- only, not is_atom(entry) do
|
||||
for entry <- List.wrap(only), not is_atom(entry) do
|
||||
Mix.raise "Expected :only in dependency to be an atom or a list of atoms, got: #{inspect only}"
|
||||
end
|
||||
only
|
||||
@@ -293,7 +293,7 @@ defmodule Mix.Dep.Loader do
|
||||
from = Path.absname("mix.exs")
|
||||
(Mix.Project.config[:deps] || [])
|
||||
|> Enum.map(&to_dep(&1, from))
|
||||
|> partition_by_env(opts)
|
||||
|> partition_by_env(opts[:env])
|
||||
|> elem(0)
|
||||
end
|
||||
|
||||
|
||||
@@ -127,8 +127,8 @@ defmodule Mix.Tasks.Compile do
|
||||
"""
|
||||
def manifests do
|
||||
Enum.flat_map(compilers(), fn(compiler) ->
|
||||
module = Mix.Task.get!("compile.#{compiler}")
|
||||
if function_exported?(module, :manifests, 0) do
|
||||
module = Mix.Task.get("compile.#{compiler}")
|
||||
if module && function_exported?(module, :manifests, 0) do
|
||||
module.manifests
|
||||
else
|
||||
[]
|
||||
|
||||
@@ -25,7 +25,7 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
"""
|
||||
|
||||
import Mix.Dep, only: [loaded: 1, available?: 1, loaded_by_name: 2,
|
||||
format_dep: 1, make?: 1, mix?: 1, rebar?: 1]
|
||||
format_dep: 1, make?: 1, mix?: 1]
|
||||
|
||||
@spec run(OptionParser.argv) :: :ok
|
||||
def run(args) do
|
||||
@@ -100,6 +100,11 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
"but you are running on v#{System.version}"
|
||||
end
|
||||
|
||||
# Force recompilation on compile status
|
||||
if dep.status == :compile do
|
||||
Mix.Dep.Lock.touch_manifest
|
||||
end
|
||||
|
||||
try do
|
||||
res = Mix.Task.run("compile", ["--no-deps", "--no-elixir-version-check"])
|
||||
:ok in List.wrap(res)
|
||||
|
||||
@@ -144,7 +144,22 @@ defmodule Mix.DepTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "nested deps convergence with optional dependencies" do
|
||||
test "nested deps with convergence and managers" do
|
||||
Process.put(:custom_deps_git_repo_opts, [manager: :make])
|
||||
|
||||
deps = [{:deps_repo, "0.1.0", path: "custom/deps_repo", manager: :rebar},
|
||||
{:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo")}]
|
||||
|
||||
with_deps deps, fn ->
|
||||
in_fixture "deps_status", fn ->
|
||||
[dep1, dep2] = Mix.Dep.loaded([])
|
||||
assert dep1.manager == nil
|
||||
assert dep2.manager == :rebar
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "nested deps with convergence and optional dependencies" do
|
||||
deps = [{:deps_repo, "0.1.0", path: "custom/deps_repo"},
|
||||
{:git_repo, "0.2.0", git: MixTest.Case.fixture_path("git_repo")}]
|
||||
|
||||
@@ -255,9 +270,8 @@ defmodule Mix.DepTest do
|
||||
deps = Mix.Dep.loaded([])
|
||||
assert length(deps) == 2
|
||||
|
||||
deps = Mix.Dep.loaded([env: :prod])
|
||||
assert length(deps) == 1
|
||||
assert Enum.find deps, &match?(%Mix.Dep{app: :foo}, &1)
|
||||
assert [dep] = Mix.Dep.loaded([env: :prod])
|
||||
assert dep.app == :foo
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,6 +26,12 @@ defmodule Mix.Tasks.CompileTest do
|
||||
assert_received {:mix_shell, :info, ["\nEnabled compilers: elixir, app, custom, protocols"]}
|
||||
end
|
||||
|
||||
test "compile does not require all compilers available on manifest" do
|
||||
Mix.Project.push CustomCompilers
|
||||
assert Mix.Tasks.Compile.manifests |> Enum.map(&Path.basename/1) ==
|
||||
[".compile.elixir"]
|
||||
end
|
||||
|
||||
test "compile a project with mixfile" do
|
||||
in_fixture "no_mixfile", fn ->
|
||||
assert Mix.Tasks.Compile.run([]) == :ok
|
||||
|
||||
@@ -146,9 +146,6 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
|
||||
# Clear up to prepare for the update
|
||||
File.rm("_build/dev/lib/git_repo/ebin/Elixir.GitRepo.beam")
|
||||
File.rm("_build/dev/lib/git_repo/.compile.elixir")
|
||||
File.rm("deps/git_repo/.fetch")
|
||||
Mix.Task.clear
|
||||
Mix.shell.flush
|
||||
purge [A, B, C, GitRepo]
|
||||
|
||||
@@ -113,15 +113,19 @@ defmodule Mix.UmbrellaTest do
|
||||
end
|
||||
"""
|
||||
|
||||
# Should work across all environments
|
||||
# Does not fetch when filtered
|
||||
Mix.Tasks.Deps.Get.run ["--only", "dev"]
|
||||
refute_received {:mix_shell, :info, ["* Getting git_repo" <> _]}
|
||||
|
||||
# But works across all environments
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
assert_received {:mix_shell, :info, ["* Getting git_repo" <> _]}
|
||||
|
||||
# Works on the current environment only
|
||||
# Does not show by default
|
||||
Mix.Tasks.Deps.run []
|
||||
refute_received {:mix_shell, :info, ["* git_repo " <> _]}
|
||||
refute_received {:mix_shell, :info, ["* git_repo" <> _]}
|
||||
|
||||
# Works on the other environment only
|
||||
# But shows on proper environment
|
||||
Mix.env(:other)
|
||||
Mix.Tasks.Deps.run []
|
||||
assert_received {:mix_shell, :info, ["* git_repo " <> _]}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{application, elixir,
|
||||
[{description, "elixir"},
|
||||
{vsn, "1.2.0"},
|
||||
{vsn, "1.2.2"},
|
||||
{modules, [
|
||||
elixir
|
||||
]},
|
||||
|
||||
Reference in New Issue
Block a user