Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58ca6524f8 | ||
|
|
afcefb3e21 | ||
|
|
fb5aabf05a | ||
|
|
d9a81101dd | ||
|
|
8fca53ad31 | ||
|
|
76dfedb2e3 | ||
|
|
8bec593f5d | ||
|
|
03859fb92e | ||
|
|
53e49f33f1 | ||
|
|
c0bdd6101f | ||
|
|
e7cb210712 | ||
|
|
4a4657e413 | ||
|
|
fcada17819 | ||
|
|
6be8bddc02 | ||
|
|
9feda914dc | ||
|
|
62e1d71377 | ||
|
|
d6ed6d18ef | ||
|
|
e024a12c50 |
@@ -90,6 +90,31 @@ As of Elixir v1.12, implicitly decreasing ranges are soft-deprecated and warning
|
||||
|
||||
Elixir v1.12 has the additional of many functions across the standard library. The `Enum` module received additions such as `Enum.count_until/2`, `Enum.product/1`, `Enum.zip_with/2`, and more. The `Integer` module now includes `Integer.pow/2` and `Integer.extended_gcd/2`. Finally, the `Kernel` module got two new functions, `Kernel.then/2` and `Kernel.tap/2`, which are specially useful in `|>` pipelines.
|
||||
|
||||
## v1.12.2 (2021-07-01)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Ensure deprecated macros emit warnings
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix deps] Ensure unconstrained rebar deps generate valid mix specifications
|
||||
|
||||
### 2. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [elixirc] Change the output of `--profile time` to make it easier to detect outliers
|
||||
* [Application] Do not add compile time deps on args to `Application.compile_env/2` and `Application.compile_env!/2`
|
||||
* [Enum] Optimize `Enum.into/3` and `Map.new/2`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Compile most recently changed files first
|
||||
* [mix compile, mix run, mix test] Speed up the time taken to load dependencies. This should make the usage of Mix inside projects quite more responsive
|
||||
|
||||
## v1.12.1 (2021-05-28)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
@@ -507,11 +507,22 @@ defmodule Application do
|
||||
raise "Application.compile_env/3 cannot be called inside functions, only in the module body"
|
||||
end
|
||||
|
||||
key_or_path = expand_key_or_path(key_or_path, __CALLER__)
|
||||
|
||||
quote do
|
||||
Application.__compile_env__(unquote(app), unquote(key_or_path), unquote(default), __ENV__)
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_key_or_path({:__aliases__, _, _} = alias, env),
|
||||
do: Macro.expand(alias, %{env | function: {:__info__, 1}})
|
||||
|
||||
defp expand_key_or_path(list, env) when is_list(list),
|
||||
do: Enum.map(list, &expand_key_or_path(&1, env))
|
||||
|
||||
defp expand_key_or_path(other, _env),
|
||||
do: other
|
||||
|
||||
@doc false
|
||||
def __compile_env__(app, key_or_path, default, env) do
|
||||
case fetch_compile_env(app, key_or_path, env) do
|
||||
@@ -533,6 +544,8 @@ defmodule Application do
|
||||
raise "Application.compile_env!/2 cannot be called inside functions, only in the module body"
|
||||
end
|
||||
|
||||
key_or_path = expand_key_or_path(key_or_path, __CALLER__)
|
||||
|
||||
quote do
|
||||
Application.__compile_env__!(unquote(app), unquote(key_or_path), __ENV__)
|
||||
end
|
||||
|
||||
@@ -1550,6 +1550,7 @@ defmodule Code do
|
||||
@doc """
|
||||
Same as `ensure_loaded/1` but raises if the module cannot be loaded.
|
||||
"""
|
||||
@doc since: "1.12.0"
|
||||
@spec ensure_loaded!(module) :: module
|
||||
def ensure_loaded!(module) do
|
||||
case ensure_loaded(module) do
|
||||
|
||||
+44
-15
@@ -1421,28 +1421,35 @@ defmodule Enum do
|
||||
into_protocol(enumerable, collectable)
|
||||
end
|
||||
|
||||
def into(%{} = enumerable, %{} = collectable) do
|
||||
Map.merge(collectable, enumerable)
|
||||
end
|
||||
|
||||
def into(enumerable, %{} = collectable) when is_list(enumerable) do
|
||||
Map.merge(collectable, :maps.from_list(enumerable))
|
||||
end
|
||||
|
||||
def into(enumerable, %{} = collectable) do
|
||||
reduce(enumerable, collectable, fn {key, val}, acc ->
|
||||
Map.put(acc, key, val)
|
||||
end)
|
||||
if map_size(collectable) == 0 do
|
||||
into_map(enumerable)
|
||||
else
|
||||
into_map(enumerable, collectable)
|
||||
end
|
||||
end
|
||||
|
||||
def into(enumerable, collectable) do
|
||||
into_protocol(enumerable, collectable)
|
||||
end
|
||||
|
||||
defp into_map(%{} = enumerable), do: enumerable
|
||||
defp into_map(enumerable) when is_list(enumerable), do: :maps.from_list(enumerable)
|
||||
defp into_map(enumerable), do: enumerable |> Enum.to_list() |> :maps.from_list()
|
||||
|
||||
defp into_map(%{} = enumerable, collectable),
|
||||
do: Map.merge(collectable, enumerable)
|
||||
|
||||
defp into_map(enumerable, collectable) when is_list(enumerable),
|
||||
do: Map.merge(collectable, :maps.from_list(enumerable))
|
||||
|
||||
defp into_map(enumerable, collectable),
|
||||
do: Enum.reduce(enumerable, collectable, fn {key, val}, acc -> Map.put(acc, key, val) end)
|
||||
|
||||
defp into_protocol(enumerable, collectable) do
|
||||
{initial, fun} = Collectable.into(collectable)
|
||||
|
||||
into(enumerable, initial, fun, fn entry, acc ->
|
||||
into_protocol(enumerable, initial, fun, fn entry, acc ->
|
||||
fun.(acc, {:cont, entry})
|
||||
end)
|
||||
end
|
||||
@@ -1461,20 +1468,42 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec into(Enumerable.t(), Collectable.t(), (term -> term)) :: Collectable.t()
|
||||
|
||||
def into(enumerable, collectable, transform) when is_list(collectable) do
|
||||
collectable ++ map(enumerable, transform)
|
||||
end
|
||||
|
||||
def into(%_{} = enumerable, collectable, transform) do
|
||||
into_protocol(enumerable, collectable, transform)
|
||||
end
|
||||
|
||||
def into(enumerable, %_{} = collectable, transform) do
|
||||
into_protocol(enumerable, collectable, transform)
|
||||
end
|
||||
|
||||
def into(enumerable, %{} = collectable, transform) do
|
||||
if map_size(collectable) == 0 do
|
||||
enumerable |> Enum.map(transform) |> :maps.from_list()
|
||||
else
|
||||
Enum.reduce(enumerable, collectable, fn entry, acc ->
|
||||
{key, val} = transform.(entry)
|
||||
Map.put(acc, key, val)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def into(enumerable, collectable, transform) do
|
||||
into_protocol(enumerable, collectable, transform)
|
||||
end
|
||||
|
||||
defp into_protocol(enumerable, collectable, transform) do
|
||||
{initial, fun} = Collectable.into(collectable)
|
||||
|
||||
into(enumerable, initial, fun, fn entry, acc ->
|
||||
into_protocol(enumerable, initial, fun, fn entry, acc ->
|
||||
fun.(acc, {:cont, transform.(entry)})
|
||||
end)
|
||||
end
|
||||
|
||||
defp into(enumerable, initial, fun, callback) do
|
||||
defp into_protocol(enumerable, initial, fun, callback) do
|
||||
try do
|
||||
reduce(enumerable, initial, callback)
|
||||
catch
|
||||
|
||||
@@ -516,6 +516,7 @@ defmodule IO do
|
||||
IO.stream(:stdio, :line)
|
||||
|
||||
"""
|
||||
@doc since: "1.12.0"
|
||||
def stream, do: stream(:stdio, :line)
|
||||
|
||||
@doc """
|
||||
@@ -534,6 +535,9 @@ defmodule IO do
|
||||
Note that an IO stream has side effects and every time
|
||||
you go over the stream you may get different results.
|
||||
|
||||
`stream/1` has been introduced in Elixir v1.12.0,
|
||||
while `stream/2` has been available since v1.0.0.
|
||||
|
||||
## Examples
|
||||
|
||||
Here is an example on how we mimic an echo server
|
||||
@@ -557,6 +561,7 @@ defmodule IO do
|
||||
IO.binstream(:stdio, :line)
|
||||
|
||||
"""
|
||||
@doc since: "1.12.0"
|
||||
def binstream, do: binstream(:stdio, :line)
|
||||
|
||||
@doc """
|
||||
@@ -574,6 +579,9 @@ defmodule IO do
|
||||
|
||||
Finally, do not use this function on IO devices in Unicode
|
||||
mode as it will return the wrong result.
|
||||
|
||||
`binstream/1` has been introduced in Elixir v1.12.0,
|
||||
while `binstream/2` has been available since v1.0.0.
|
||||
"""
|
||||
@spec binstream(device, :line | pos_integer) :: Enumerable.t()
|
||||
def binstream(device \\ :stdio, line_or_bytes)
|
||||
|
||||
@@ -5554,7 +5554,7 @@ defmodule Kernel do
|
||||
quote(do: List.to_charlist(unquote(unescape_list_tokens(pieces))))
|
||||
end
|
||||
|
||||
@doc """
|
||||
@doc ~S"""
|
||||
Handles the sigil `~r` for regular expressions.
|
||||
|
||||
It returns a regular expression pattern, unescaping characters and replacing
|
||||
@@ -5564,12 +5564,15 @@ defmodule Kernel do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.match?(~r(foo), "foo")
|
||||
iex> Regex.match?(~r/foo/, "foo")
|
||||
true
|
||||
|
||||
iex> Regex.match?(~r/a#{:b}c/, "abc")
|
||||
true
|
||||
|
||||
While the `~r` sigil allows parens and brackets to be used as delimiters,
|
||||
it is preferred to use `"` or `/` to avoid escaping conflicts with reserved
|
||||
regex characters.
|
||||
"""
|
||||
defmacro sigil_r(term, modifiers)
|
||||
|
||||
|
||||
@@ -623,13 +623,13 @@ defmodule Kernel.ParallelCompiler do
|
||||
data = maybe_warn_long_compilation(data, state)
|
||||
|
||||
if state.profile != :none do
|
||||
compiling = System.convert_time_unit(data.compiling, :native, :millisecond)
|
||||
waiting = System.convert_time_unit(data.waiting, :native, :millisecond)
|
||||
extra = if waiting > 0, do: " (plus #{waiting}ms waiting)", else: ""
|
||||
compiling = to_padded_ms(data.compiling)
|
||||
waiting = to_padded_ms(data.waiting)
|
||||
relative = Path.relative_to_cwd(data.file)
|
||||
|
||||
IO.puts(
|
||||
:stderr,
|
||||
"[profile] #{Path.relative_to_cwd(data.file)} compiled in #{compiling}ms" <> extra
|
||||
"[profile] #{compiling}ms compiling + #{waiting}ms waiting for #{relative}"
|
||||
)
|
||||
end
|
||||
|
||||
@@ -640,6 +640,13 @@ defmodule Kernel.ParallelCompiler do
|
||||
end)
|
||||
end
|
||||
|
||||
defp to_padded_ms(time) do
|
||||
time
|
||||
|> System.convert_time_unit(:native, :millisecond)
|
||||
|> Integer.to_string()
|
||||
|> String.pad_leading(6, " ")
|
||||
end
|
||||
|
||||
defp discard_down(pid) do
|
||||
receive do
|
||||
{:DOWN, _, :process, ^pid, _} -> :ok
|
||||
|
||||
@@ -105,7 +105,9 @@ defmodule Macro.Env do
|
||||
vars: vars
|
||||
}
|
||||
|
||||
# Define the __struct__ callbacks by hand for bootstrap reasons.
|
||||
# TODO: Remove :vars field on v2.0
|
||||
@doc false
|
||||
def __struct__ do
|
||||
%{
|
||||
__struct__: __MODULE__,
|
||||
@@ -130,6 +132,7 @@ defmodule Macro.Env do
|
||||
}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __struct__(kv) do
|
||||
Enum.reduce(kv, __struct__(), fn {k, v}, acc -> :maps.update(k, v, acc) end)
|
||||
end
|
||||
|
||||
+1
-11
@@ -216,20 +216,10 @@ defmodule Map do
|
||||
@spec new(Enumerable.t(), (term -> {key, value})) :: map
|
||||
def new(enumerable, transform) when is_function(transform, 1) do
|
||||
enumerable
|
||||
|> Enum.to_list()
|
||||
|> new_transform(transform, [])
|
||||
end
|
||||
|
||||
defp new_transform([], _fun, acc) do
|
||||
acc
|
||||
|> :lists.reverse()
|
||||
|> Enum.map(transform)
|
||||
|> :maps.from_list()
|
||||
end
|
||||
|
||||
defp new_transform([element | rest], fun, acc) do
|
||||
new_transform(rest, fun, [fun.(element) | acc])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns whether the given `key` exists in the given `map`.
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ require_function(Meta, Receiver, Name, Arity, E) ->
|
||||
end.
|
||||
|
||||
remote_function(Meta, Receiver, Name, Arity, E) ->
|
||||
check_deprecated(Meta, Receiver, Name, Arity, E),
|
||||
check_deprecated(Meta, function, Receiver, Name, Arity, E),
|
||||
|
||||
case elixir_rewrite:inline(Receiver, Name, Arity) of
|
||||
{AR, AN} -> {remote, AR, AN, Arity};
|
||||
@@ -161,7 +161,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?key(E, function)),
|
||||
{ok, Receiver, Name, Args};
|
||||
{macro, Receiver} ->
|
||||
check_deprecated(Meta, Receiver, Name, Arity, E),
|
||||
check_deprecated(Meta, macro, Receiver, Name, Arity, E),
|
||||
elixir_env:trace({imported_macro, Meta, Receiver, Name, Arity}, E),
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?key(E, function)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
@@ -180,17 +180,18 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
end.
|
||||
|
||||
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
|
||||
check_deprecated(Meta, Receiver, Name, Arity, E),
|
||||
Required = (Receiver == ?key(E, module)) orelse required(Meta) orelse is_element(Receiver, ?key(E, requires)),
|
||||
|
||||
case is_element(Tuple, get_macros(Receiver, Required)) of
|
||||
true when Required ->
|
||||
check_deprecated(Meta, macro, Receiver, Name, Arity, E),
|
||||
elixir_env:trace({remote_macro, Meta, Receiver, Name, Arity}, E),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
true ->
|
||||
Info = {unrequired_module, {Receiver, Name, length(Args)}},
|
||||
elixir_errors:form_error(Meta, E, ?MODULE, Info);
|
||||
false ->
|
||||
check_deprecated(Meta, function, Receiver, Name, Arity, E),
|
||||
error
|
||||
end.
|
||||
|
||||
@@ -361,12 +362,13 @@ elixir_imported_macros() ->
|
||||
error:undef -> []
|
||||
end.
|
||||
|
||||
check_deprecated(_, erlang, _, _, _) ->
|
||||
ok;
|
||||
check_deprecated(_, ?kernel, _, _, _) ->
|
||||
ok;
|
||||
check_deprecated(Meta, Receiver, Name, Arity, E) ->
|
||||
case (?key(E, function) == nil) andalso get_deprecations(Receiver) of
|
||||
check_deprecated(_, _, erlang, _, _, _) -> ok;
|
||||
check_deprecated(_, _, elixir_def, _, _, _) -> ok;
|
||||
check_deprecated(_, _, elixir_module, _, _, _) -> ok;
|
||||
check_deprecated(_, _, ?kernel, _, _, _) -> ok;
|
||||
check_deprecated(Meta, Kind, Receiver, Name, Arity, E) ->
|
||||
%% Any compile time behaviour cannot be verified by the runtime group pass.
|
||||
case ((?key(E, function) == nil) or (Kind == macro)) andalso get_deprecations(Receiver) of
|
||||
[_ | _] = Deprecations ->
|
||||
case lists:keyfind({Name, Arity}, 1, Deprecations) of
|
||||
{_, Message} ->
|
||||
|
||||
@@ -223,9 +223,9 @@ translate({{'.', _, [Left, Right]}, Meta, []}, S) when is_tuple(Left), is_atom(R
|
||||
Ann = ?ann(Meta),
|
||||
TRight = {atom, Ann, Right},
|
||||
|
||||
{Var, SV} = elixir_erl_var:build('_', SL),
|
||||
TVar = {var, Ann, Var},
|
||||
Generated = erl_anno:set_generated(true, Ann),
|
||||
{Var, SV} = elixir_erl_var:build('_', SL),
|
||||
TVar = {var, Generated, Var},
|
||||
TError = {tuple, Ann, [{atom, Ann, badkey}, TRight, TVar]},
|
||||
|
||||
{{'case', Generated, TLeft, [
|
||||
|
||||
@@ -396,6 +396,8 @@ defmodule EnumTest do
|
||||
test "into/2" do
|
||||
assert Enum.into([a: 1, b: 2], %{}) == %{a: 1, b: 2}
|
||||
assert Enum.into([a: 1, b: 2], %{c: 3}) == %{a: 1, b: 2, c: 3}
|
||||
assert Enum.into(MapSet.new(a: 1, b: 2), %{}) == %{a: 1, b: 2}
|
||||
assert Enum.into(MapSet.new(a: 1, b: 2), %{c: 3}) == %{a: 1, b: 2, c: 3}
|
||||
assert Enum.into(%{a: 1, b: 2}, []) == [a: 1, b: 2]
|
||||
assert Enum.into(1..3, []) == [1, 2, 3]
|
||||
assert Enum.into(["H", "i"], "") == "Hi"
|
||||
@@ -405,7 +407,7 @@ defmodule EnumTest do
|
||||
assert Enum.into([1, 2, 3], [], fn x -> x * 2 end) == [2, 4, 6]
|
||||
assert Enum.into([1, 2, 3], "numbers: ", &to_string/1) == "numbers: 123"
|
||||
|
||||
assert_raise FunctionClauseError, fn ->
|
||||
assert_raise MatchError, fn ->
|
||||
Enum.into([2, 3], %{a: 1}, & &1)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -202,5 +202,20 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
assert URI in exports
|
||||
refute URI in runtime
|
||||
end
|
||||
|
||||
test "compile_env! does not add a compile dependency" do
|
||||
{{compile, exports, runtime, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
defmodule Kernel.LexicalTrackerTest.CompileEnvStruct do
|
||||
Application.compile_env(:elixir, URI)
|
||||
Application.compile_env(:elixir, [:foo, URI, :bar])
|
||||
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
|
||||
end |> elem(3)
|
||||
""")
|
||||
|
||||
refute URI in compile
|
||||
refute URI in exports
|
||||
assert URI in runtime
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,7 +43,9 @@ defmodule Kernel.ParallelCompilerTest do
|
||||
assert HelloWorld in modules
|
||||
end)
|
||||
|
||||
assert profile =~ ~r"\[profile\] .*tmp/profile_time/bar.ex compiled in \d+ms"
|
||||
assert profile =~
|
||||
~r"\[profile\] [\s\d]{6}ms compiling \+ 0ms waiting for .*tmp/profile_time/bar.ex"
|
||||
|
||||
assert profile =~ ~r"\[profile\] Finished compilation cycle of 1 modules in \d+ms"
|
||||
assert profile =~ ~r"\[profile\] Finished group pass check of 1 modules in \d+ms"
|
||||
after
|
||||
|
||||
@@ -663,6 +663,31 @@ defmodule Module.Types.IntegrationTest do
|
||||
|
||||
assert_warnings(files, warning)
|
||||
end
|
||||
|
||||
test "reports macro" do
|
||||
files = %{
|
||||
"a.ex" => """
|
||||
defmodule A do
|
||||
@deprecated "oops"
|
||||
defmacro a, do: :ok
|
||||
end
|
||||
""",
|
||||
"b.ex" => """
|
||||
defmodule B do
|
||||
require A
|
||||
def b, do: A.a()
|
||||
end
|
||||
"""
|
||||
}
|
||||
|
||||
warning = """
|
||||
warning: A.a/0 is deprecated. oops
|
||||
b.ex:3: B.b/0
|
||||
|
||||
"""
|
||||
|
||||
assert_warnings(files, warning)
|
||||
end
|
||||
end
|
||||
|
||||
defp assert_warnings(files, expected) when is_binary(expected) do
|
||||
|
||||
@@ -226,6 +226,15 @@ defmodule Mix.Compilers.Elixir do
|
||||
dest
|
||||
)
|
||||
|
||||
# Now sort the files so the ones changed more recently come first.
|
||||
# We do an optimized version of sort_by since we don't care about
|
||||
# stable sorting.
|
||||
changed =
|
||||
changed
|
||||
|> Enum.map(&{-elem(Map.fetch!(sources_stats, &1), 0), &1})
|
||||
|> Enum.sort()
|
||||
|> Enum.map(&elem(&1, 1))
|
||||
|
||||
{modules, exports, changed, sources_stats}
|
||||
end
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ defmodule Mix.Rebar do
|
||||
# Translate a Rebar dependency declaration to a Mix declaration
|
||||
# From http://www.rebar3.org/docs/dependencies#section-declaring-dependencies
|
||||
defp parse_dep(app) when is_atom(app) do
|
||||
{app, nil, override: true}
|
||||
{app, override: true}
|
||||
end
|
||||
|
||||
defp parse_dep({app, req}) when is_list(req) do
|
||||
|
||||
@@ -14,6 +14,7 @@ defmodule Mix.Tasks.Compile.All do
|
||||
Mix.Project.get!()
|
||||
config = Mix.Project.config()
|
||||
validate_compile_env? = "--no-validate-compile-env" not in args
|
||||
lib_path = Path.join(Mix.Project.build_path(config), "lib")
|
||||
|
||||
# Make sure Mix.Dep is cached to avoid loading dependencies
|
||||
# during compilation. It is likely this will be invoked anyway,
|
||||
@@ -21,7 +22,7 @@ defmodule Mix.Tasks.Compile.All do
|
||||
Mix.Dep.cached()
|
||||
|
||||
unless "--no-app-loading" in args do
|
||||
load_apps(config, validate_compile_env?)
|
||||
load_apps(config, lib_path, validate_compile_env?)
|
||||
end
|
||||
|
||||
result =
|
||||
@@ -39,8 +40,9 @@ defmodule Mix.Tasks.Compile.All do
|
||||
end)
|
||||
end
|
||||
|
||||
app = config[:app]
|
||||
_ = Code.prepend_path(Mix.Project.compile_path())
|
||||
load_app(config[:app], validate_compile_env?)
|
||||
load_app(app, lib_path, validate_compile_env?)
|
||||
result
|
||||
end
|
||||
|
||||
@@ -93,44 +95,63 @@ defmodule Mix.Tasks.Compile.All do
|
||||
|
||||
## App loading helpers
|
||||
|
||||
defp load_apps(config, validate_compile_env?) do
|
||||
defp load_apps(config, lib_path, validate_compile_env?) do
|
||||
{runtime, optional} = Mix.Tasks.Compile.App.project_apps(config)
|
||||
parent = self()
|
||||
opts = [ordered: false, timeout: :infinity]
|
||||
deps = for dep <- Mix.Dep.cached(), into: %{}, do: {dep.app, lib_path}
|
||||
|
||||
%{}
|
||||
|> load_apps(runtime, validate_compile_env?)
|
||||
|> load_apps(optional, validate_compile_env?)
|
||||
stream_apps(runtime ++ optional, deps)
|
||||
|> Task.async_stream(&load_stream_app(&1, parent, validate_compile_env?), opts)
|
||||
|> Stream.run()
|
||||
end
|
||||
|
||||
defp load_stream_app({app, lib_path}, parent, validate_compile_env?) do
|
||||
children =
|
||||
case load_app(app, lib_path, validate_compile_env?) do
|
||||
:ok ->
|
||||
Application.spec(app, :applications) ++ Application.spec(app, :included_applications)
|
||||
|
||||
:error ->
|
||||
[]
|
||||
end
|
||||
|
||||
send(parent, {:done, app, children})
|
||||
:ok
|
||||
end
|
||||
|
||||
defp load_apps(seen, apps, validate_compile_env?) do
|
||||
Enum.reduce(apps, seen, fn app, seen ->
|
||||
if Map.has_key?(seen, app) do
|
||||
seen
|
||||
else
|
||||
seen = Map.put(seen, app, true)
|
||||
|
||||
case load_app(app, validate_compile_env?) do
|
||||
:ok ->
|
||||
seen
|
||||
|> load_apps(Application.spec(app, :applications), validate_compile_env?)
|
||||
|> load_apps(Application.spec(app, :included_applications), validate_compile_env?)
|
||||
|
||||
:error ->
|
||||
seen
|
||||
end
|
||||
end
|
||||
end)
|
||||
defp stream_apps(initial, deps) do
|
||||
Stream.unfold({initial, %{}, %{}, deps}, &stream_app/1)
|
||||
end
|
||||
|
||||
defp load_app(app, validate_compile_env?) do
|
||||
# We already processed this app, skip it.
|
||||
defp stream_app({[app | apps], seen, done, deps}) when is_map_key(seen, app) do
|
||||
stream_app({apps, seen, done, deps})
|
||||
end
|
||||
|
||||
# We haven't processed this app, emit it.
|
||||
defp stream_app({[app | apps], seen, done, deps}) do
|
||||
{{app, deps[app]}, {apps, Map.put(seen, app, true), done, deps}}
|
||||
end
|
||||
|
||||
# We have processed all apps and all seen have been done.
|
||||
defp stream_app({[], seen, done, _deps}) when map_size(seen) == map_size(done) do
|
||||
nil
|
||||
end
|
||||
|
||||
# We have processed all apps but there is work being done.
|
||||
defp stream_app({[], seen, done, deps}) do
|
||||
receive do
|
||||
{:done, app, children} -> stream_app({children, seen, Map.put(done, app, true), deps})
|
||||
end
|
||||
end
|
||||
|
||||
defp load_app(app, lib_path, validate_compile_env?) do
|
||||
if Application.spec(app, :vsn) do
|
||||
:ok
|
||||
else
|
||||
name = Atom.to_charlist(app) ++ '.app'
|
||||
|
||||
with [_ | _] = path <- :code.where_is_file(name),
|
||||
{:ok, {:application, _, properties} = application_data} <- consult_app_file(path),
|
||||
with {:ok, bin} <- read_app(app, lib_path),
|
||||
{:ok, {:application, _, properties} = application_data} <- consult_app_file(bin),
|
||||
:ok <- :application.load(application_data) do
|
||||
if compile_env = validate_compile_env? && properties[:compile_env] do
|
||||
Config.Provider.validate_compile_env(compile_env, false)
|
||||
@@ -143,10 +164,22 @@ defmodule Mix.Tasks.Compile.All do
|
||||
end
|
||||
end
|
||||
|
||||
defp consult_app_file(path) do
|
||||
# The app didn't come from a dep, go through the slow path (code/erl_prim_loader)
|
||||
defp read_app(app, nil) do
|
||||
name = Atom.to_charlist(app) ++ '.app'
|
||||
|
||||
with [_ | _] = path <- :code.where_is_file(name),
|
||||
{:ok, bin, _full_name} <- :erl_prim_loader.get_file(path),
|
||||
do: {:ok, bin}
|
||||
end
|
||||
|
||||
defp read_app(app, lib_path) do
|
||||
File.read("#{lib_path}/#{app}/ebin/#{app}.app")
|
||||
end
|
||||
|
||||
defp consult_app_file(bin) do
|
||||
# The path could be located in an .ez archive, so we use the prim loader.
|
||||
with {:ok, bin, _full_name} <- :erl_prim_loader.get_file(path),
|
||||
{:ok, tokens, _} <- :erl_scan.string(String.to_charlist(bin)) do
|
||||
with {:ok, tokens, _} <- :erl_scan.string(String.to_charlist(bin)) do
|
||||
:erl_parse.parse_term(tokens)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -112,6 +112,8 @@ defmodule Mix.RebarTest do
|
||||
|
||||
assert parse_dep({:git_rebar, '', {:git, @git_rebar_charlist, {:ref, '64691eb'}}}) ==
|
||||
{:git_rebar, ~r"", override: true, git: @git_rebar_string, ref: "64691eb"}
|
||||
|
||||
assert parse_dep(:git_rebar) == {:git_rebar, override: true}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user