Compare commits

...
16 Commits
Author SHA1 Message Date
José Valim 6d98af49c9 v1.7 and v1.8 are compatible with Erlang/OTP 22 2019-05-14 14:39:45 +02:00
José Valim 98485daab0 Release v1.8.2 2019-05-11 14:29:01 +02:00
José Valim 59f47949a9 Make sure Logger v1.8 does not get stuck in discard mode (#9029) 2019-05-11 10:04:38 +02:00
Mitchell Henke fca5f1cf73 Translate process crash on node in Logger (#9020)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-05-08 13:10:15 +02:00
José Valim 153ec434de Raise on bad EEx state, closes #8790 (#8976) 2019-04-23 11:55:10 +02:00
José Valim 061a989935 Move DebugInfo test into its own sync case 2019-03-31 11:11:00 +02:00
José Valim 21f10f7296 Ensure debug_info is kept in protocols, closes #8923 2019-03-31 10:58:08 +02:00
John Jacob 336452f81b Fix typo 'alwaus' -> 'always' (#8760) 2019-02-03 07:49:56 +01:00
José Valim 511a51ba89 Release v1.8.1 2019-01-30 11:37:58 +01:00
Eksperimental 7e4f9c5647 IEx h: sort results by arity (#8727)
The results were not sorted, when calling:
h Module.function_name

Example:

```
iex)> h :erlang.float_to_binary
                           :erlang.float_to_binary/2

  @spec float_to_binary(float, options) :: binary()
        when float: float(),
             options: [option],
             option:
               {:decimals, decimals :: 0..253}
               | {:scientific, decimals :: 0..249}
               | :compact

Module was compiled without docs. Showing only specs.

                           :erlang.float_to_binary/1

  @spec float_to_binary(float) :: binary() when float: float()

Module was compiled without docs. Showing only specs.
```

Now the results are sorted by arity.
2019-01-29 20:28:04 +01:00
Sfusato b37ecb0f8e Add IEx warning when using --remsh with 'dumb' terminal (#8563)
Closes #8562
2019-01-29 20:27:31 +01:00
José Valim ac947a5d1d Revert "Include optional dependencies in extra_applications (#8263)"
Unfortunately adding optional dependencies doesn't work for umbrella
apps where each app has a different optional dependency. For example,
Ecto 3.0 has both jason and poison as optional deps. Imagine the two
umbrella children below:

    foo
      * ecto
      * jason

    bar
      * ecto
      * poison
      * jason

Because ecto is shared with both, Ecto will include both poison and
jason, which makes`foo` fail to boot when running in isolation.

Closes #7930.
2019-01-29 19:57:58 +01:00
Tobiasz Małecki 82a511aafe Fix IEx.pry crash when IEx (IEx.Broker) isn't running (#8730) 2019-01-29 08:12:39 +01:00
José Valim 7c2e65f418 Do not rely on timezone database for DateTime.now
The FakeTimeZoneDatabase doesn't have all timezone entries,
which means it would be just a matter of time for this test
to start failing.

Closes #8702.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-01-25 10:12:27 +01:00
José Valim 3d04cb0aa0 Fix rounding for subnormal floats (#8687)
Closes #8685

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2019-01-25 08:42:01 +01:00
Fernando Tapia Rico 8fa9d728f1 Correct release date of v1.8 in CHANGELOG (#8646)
[ci skip]
2019-01-18 10:16:49 +01:00
24 changed files with 328 additions and 89 deletions
+36 -1
View File
@@ -63,7 +63,42 @@ When a task is spawned directly from your code, without a supervisor, then the p
This small feature is very powerful. It allows instrumentation and monitoring tools to better track and relate the events happening in your system. This feature can also be used by tools like the "Ecto Sandbox". The "Ecto Sandbox" allows developers to run tests concurrently against the database, by using transactions and an ownership mechanism where each process explicitly gets a connection assigned to it. Without `$callers`, every time you spawned a task that queries the database, the task would not know its caller, and therefore it would be unable to know which connection was assigned to it. This often meant features that relies on tasks could not be tested concurrently. With `$callers`, figuring out this relationship is trivial and you have more tests using the full power of your machine.
## v1.8.0 (2018-01-14)
## v1.8.2 (2019-05-11)
### 1. Bug fixes
#### EEx
* [EEx] Raise readable error message on bad EEx state
#### Elixir
* [Protocol] Ensure `:debug_info` is kept in protocols
#### Logger
* [Logger] Make sure Logger v1.8 does not get stuck in discard mode
* [Logger.Translator] Translate remote process crash in Logger
## v1.8.1 (2019-01-30)
### 1. Bug fixes
#### Elixir
* [Float] Fix rounding for subnormal floats
#### IEx
* [IEx] Fix `IEx.pry` crash when IEx isn't running
* [IEx.CLI] Add IEx warning when using `--remsh` with dumb terminal
* [IEx.Helpers] Sort results by arity on `h` helper
#### Mix
* [mix compile] Do not include optional dependencies in extra applications as it is incompatible with shared deps in umbrellas
## v1.8.0 (2019-01-14)
### 1. Enhancements
+1 -1
View File
@@ -1 +1 @@
1.8.0
1.8.2
+9
View File
@@ -158,6 +158,7 @@ defmodule EEx.Engine do
@doc false
def handle_begin(state) do
check_state!(state)
%{state | binary: [], dynamic: []}
end
@@ -168,6 +169,7 @@ defmodule EEx.Engine do
@doc false
def handle_body(state) do
check_state!(state)
%{binary: binary, dynamic: dynamic} = state
binary = {:<<>>, [], Enum.reverse(binary)}
dynamic = [binary | dynamic]
@@ -207,4 +209,11 @@ defmodule EEx.Engine do
raise EEx.SyntaxError,
"unsupported EEx syntax <%#{marker} %> (the syntax is valid but not supported by the current EEx engine)"
end
defp check_state!(%{binary: _, dynamic: _, vars_count: _}), do: :ok
defp check_state!(state) do
raise "unexpected EEx.Engine state: #{inspect(state)}. " <>
"This typically means a bug or an outdated EEx.Engine or tool"
end
end
+4 -2
View File
@@ -480,9 +480,11 @@ defmodule DateTime do
## Examples
iex> {:ok, datetime} = DateTime.now("Europe/Copenhagen", FakeTimeZoneDatabase)
iex> {:ok, datetime} = DateTime.now("Etc/UTC")
iex> datetime.time_zone
"Europe/Copenhagen"
"Etc/UTC"
iex> DateTime.now("Europe/Copenhagen")
{:error, :utc_only_time_zone_database}
iex> DateTime.now("not a real time zone name", FakeTimeZoneDatabase)
{:error, :time_zone_not_found}
+4 -6
View File
@@ -268,16 +268,14 @@ defmodule Float do
raise ArgumentError, invalid_precision_message(precision)
end
defp round(0.0, _precision, _rounding), do: 0.0
defp round(float, precision, rounding) do
<<sign::1, exp::11, significant::52-bitstring>> = <<float::float>>
{num, count, _} = decompose(significant, 1)
count = count - exp + 1023
cond do
# There is no decimal precision on subnormal floats
count <= 0 or exp == 0 ->
float
# Precision beyond 15 digits
count >= 104 ->
case rounding do
@@ -444,11 +442,11 @@ defmodule Float do
{acc, last_count, last_power}
end
@compile {:inline, sign: 2, shift_left: 2}
defp sign(0, num), do: num
defp sign(1, num), do: -num
defp shift_left(num, 0), do: num
defp shift_left(num, times), do: shift_left(num <<< 1, times - 1)
defp shift_left(num, times), do: num <<< times
defp shift_right(num, 0), do: {num, 0}
defp shift_right(1, times), do: {1, times}
+1 -1
View File
@@ -135,7 +135,7 @@ defmodule GenServer do
Supervisor.start_link(children, strategy: :one_for_all)
In both cases, `Stack.start_link/1` is alwaus invoked.
In both cases, `Stack.start_link/1` is always invoked.
`use GenServer` also accepts a list of options which configures the
child specification and therefore how it runs under a supervisor.
@@ -49,8 +49,8 @@ Elixir version | Supported Erlang/OTP versions
1.4 | 18 - 19 (and Erlang/OTP 20 from v1.4.5)
1.5 | 18 - 20
1.6 | 19 - 20 (and Erlang/OTP 21 from v1.6.6)
1.7 | 19 - 21
1.8 | 20 - 21
1.7 | 19 - 22
1.8 | 20 - 22
While Elixir often adds compatibility to new Erlang/OTP versions on released branches, such as support for Erlang/OTP 20 in v1.4.5, those releases usually contain the minimum changes for Elixir to run without errors. Only the next minor release, in this case v1.5.0, does effectively leverage the new features provided by the latest Erlang/OTP release.
+4 -4
View File
@@ -446,10 +446,10 @@ debug_opts(Map, Specs, Opts) ->
end.
take_debug_opts(Opts) ->
case lists:keytake(debug_info, 1, Opts) of
{value, {debug_info, true}, Rest} -> {true, Rest};
{value, {debug_info, false}, Rest} -> {false, Rest};
false -> {elixir_compiler:get_opt(debug_info), Opts}
case proplists:get_value(debug_info, Opts) of
true -> {true, proplists:delete(debug_info, Opts)};
false -> {false, proplists:delete(debug_info, Opts)};
undefined -> {elixir_compiler:get_opt(debug_info), Opts}
end.
extra_chunks_opts([], Opts) -> Opts;
+94 -32
View File
@@ -55,21 +55,41 @@ defmodule FloatTest do
assert Float.floor(1.32453e-10) === 0.0
end
test "floor/2 with precision" do
assert Float.floor(12.524235, 0) === 12.0
assert Float.floor(-12.524235, 0) === -13.0
describe "floor/2" do
test "with 0.0" do
for precision <- 0..15 do
assert Float.floor(0.0, precision) === 0.0
assert Float.floor(-0.0, precision) === -0.0
end
end
assert Float.floor(12.52, 2) === 12.51
assert Float.floor(-12.52, 2) === -12.52
test "floor/2 with precision" do
assert Float.floor(12.524235, 0) === 12.0
assert Float.floor(-12.524235, 0) === -13.0
assert Float.floor(12.524235, 2) === 12.52
assert Float.floor(-12.524235, 3) === -12.525
assert Float.floor(12.52, 2) === 12.51
assert Float.floor(-12.52, 2) === -12.52
assert Float.floor(12.32453e-20, 2) === 0.0
assert Float.floor(-12.32453e-20, 2) === -0.01
assert Float.floor(12.524235, 2) === 12.52
assert Float.floor(-12.524235, 3) === -12.525
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
Float.floor(1.1, 16)
assert Float.floor(12.32453e-20, 2) === 0.0
assert Float.floor(-12.32453e-20, 2) === -0.01
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
Float.floor(1.1, 16)
end
end
test "with subnormal floats" do
assert Float.floor(-5.0e-324, 0) === -1.0
assert Float.floor(-5.0e-324, 1) === -0.1
assert Float.floor(-5.0e-324, 2) === -0.01
assert Float.floor(-5.0e-324, 15) === -0.000000000000001
for precision <- 0..15 do
assert Float.floor(5.0e-324, precision) === 0.0
end
end
end
@@ -88,36 +108,72 @@ defmodule FloatTest do
assert Float.ceil(0.0) === 0.0
end
test "ceil/2 with precision" do
assert Float.ceil(12.524235, 0) === 13.0
assert Float.ceil(-12.524235, 0) === -12.0
describe "ceil/2" do
test "with 0.0" do
for precision <- 0..15 do
assert Float.ceil(0.0, precision) === 0.0
assert Float.ceil(-0.0, precision) === -0.0
end
end
assert Float.ceil(12.52, 2) === 12.52
assert Float.ceil(-12.52, 2) === -12.51
test "with regular floats" do
assert Float.ceil(12.524235, 0) === 13.0
assert Float.ceil(-12.524235, 0) === -12.0
assert Float.ceil(12.524235, 2) === 12.53
assert Float.ceil(-12.524235, 3) === -12.524
assert Float.ceil(12.52, 2) === 12.52
assert Float.ceil(-12.52, 2) === -12.51
assert Float.ceil(12.32453e-20, 2) === 0.01
assert Float.ceil(-12.32453e-20, 2) === 0.0
assert Float.ceil(12.524235, 2) === 12.53
assert Float.ceil(-12.524235, 3) === -12.524
assert Float.ceil(0.0, 2) === 0.0
assert Float.ceil(12.32453e-20, 2) === 0.01
assert Float.ceil(-12.32453e-20, 2) === 0.0
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
Float.ceil(1.1, 16)
assert Float.ceil(0.0, 2) === 0.0
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
Float.ceil(1.1, 16)
end
end
test "with subnormal floats" do
assert Float.ceil(5.0e-324, 0) === 1.0
assert Float.ceil(5.0e-324, 1) === 0.1
assert Float.ceil(5.0e-324, 2) === 0.01
assert Float.ceil(5.0e-324, 15) === 0.000000000000001
for precision <- 0..15 do
assert Float.ceil(-5.0e-324, precision) === -0.0
end
end
end
test "round/2" do
assert Float.round(5.5675, 3) === 5.567
assert Float.round(-5.5674, 3) === -5.567
assert Float.round(5.5, 3) === 5.5
assert Float.round(5.5e-10, 10) === 5.0e-10
assert Float.round(5.5e-10, 8) === 0.0
assert Float.round(5.0, 0) === 5.0
describe "round/2" do
test "with 0.0" do
for precision <- 0..15 do
assert Float.round(0.0, precision) === 0.0
assert Float.round(-0.0, precision) === -0.0
end
end
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
Float.round(1.1, 16)
test "with regular floats" do
assert Float.round(5.5675, 3) === 5.567
assert Float.round(-5.5674, 3) === -5.567
assert Float.round(5.5, 3) === 5.5
assert Float.round(5.5e-10, 10) === 5.0e-10
assert Float.round(5.5e-10, 8) === 0.0
assert Float.round(5.0, 0) === 5.0
assert_raise ArgumentError, "precision 16 is out of valid range of 0..15", fn ->
Float.round(1.1, 16)
end
end
test "with subnormal floats" do
for precision <- 0..15 do
assert Float.round(5.0e-324, precision) === 0.0
assert Float.round(-5.0e-324, precision) === -0.0
end
end
end
@@ -126,6 +182,12 @@ defmodule FloatTest do
assert Float.ratio(0.0) == {0, 1}
end
test "with regular floats" do
assert Float.ratio(3.14) == {7_070_651_414_971_679, 2_251_799_813_685_248}
assert Float.ratio(-3.14) == {-7_070_651_414_971_679, 2_251_799_813_685_248}
assert Float.ratio(1.5) == {3, 2}
end
test "with subnormal floats" do
assert Float.ratio(5.0e-324) ==
{1,
+19
View File
@@ -260,6 +260,25 @@ defmodule ProtocolTest do
end
end
defmodule Protocol.DebugInfoTest do
use ExUnit.Case
test "protocols always keep debug_info" do
Code.compiler_options(debug_info: false)
{:module, _, binary, _} =
defprotocol DebugInfoProto do
end
assert {:ok, {DebugInfoProto, [debug_info: debug_info]}} =
:beam_lib.chunks(binary, [:debug_info])
assert {:debug_info_v1, :elixir_erl, {:elixir_v1, _, _}} = debug_info
after
Code.compiler_options(debug_info: true)
end
end
path = Path.expand("../ebin", __DIR__)
File.mkdir_p!(path)
+7 -1
View File
@@ -82,7 +82,13 @@ defmodule IEx.Broker do
@spec take_over(binary, keyword) ::
{:ok, server :: pid, group_leader :: pid} | {:error, :no_iex | :refused}
def take_over(identifier, opts) do
GenServer.call(@name, {:take_over, identifier, opts}, :infinity)
case GenServer.whereis(@name) do
nil ->
{:error, :no_iex}
_pid ->
GenServer.call(@name, {:take_over, identifier, opts}, :infinity)
end
end
## Callbacks
+7
View File
@@ -53,6 +53,13 @@ defmodule IEx.CLI do
if tty_works?() do
:user_drv.start([:"tty_sl -c -e", tty_args()])
else
if get_remsh(:init.get_plain_arguments()) do
IO.puts(
:stderr,
"warning: the --remsh option will be ignored because IEx is running on limited shell"
)
end
:application.set_env(:stdlib, :shell_prompt_func, {__MODULE__, :prompt})
:user.start()
local_start()
+1
View File
@@ -283,6 +283,7 @@ defmodule IEx.Introspection do
true ->
module.module_info(:exports)
end
|> Enum.sort()
result =
for {^function, arity} <- exports,
+6 -6
View File
@@ -343,6 +343,12 @@ defmodule IEx.HelpersTest do
"""
assert capture_io(fn -> h(:timer.send_interval()) end) == """
* :timer.send_interval/2
@spec send_interval(time, message) :: {:ok, tRef} | {:error, reason}
when time: time(), message: term(), tRef: tref(), reason: term()
Module was compiled without docs. Showing only specs.
* :timer.send_interval/3
@spec send_interval(time, pid, message) :: {:ok, tRef} | {:error, reason}
@@ -352,12 +358,6 @@ defmodule IEx.HelpersTest do
tRef: tref(),
reason: term()
Module was compiled without docs. Showing only specs.
* :timer.send_interval/2
@spec send_interval(time, message) :: {:ok, tRef} | {:error, reason}
when time: time(), message: term(), tRef: tref(), reason: term()
Module was compiled without docs. Showing only specs.
"""
end
+1
View File
@@ -555,6 +555,7 @@ defmodule Logger do
:level,
:utc_log,
:discard_threshold,
:discard_threshold_periodic_check,
:translator_inspect_opts
]
@spec configure(keyword) :: :ok
+40 -24
View File
@@ -4,6 +4,7 @@ defmodule Logger.Config do
@behaviour :gen_event
@name __MODULE__
@table __MODULE__
@check_discard {__MODULE__, :check_discard}
def start_link do
GenServer.start_link(__MODULE__, :ok, name: @name)
@@ -76,29 +77,7 @@ defmodule Logger.Config do
end
def handle_event(_event, {state, thresholds}) do
%{mode: mode} = state
case compute_mode(mode, thresholds) do
^mode ->
{:ok, {state, thresholds}}
new_mode ->
if new_mode == :discard do
message =
"Logger has #{message_queue_length()} messages in its queue, " <>
"which is above :discard_threshold. Messages will be discarded " <>
"until the message queue goes back to 75% of the threshold size"
log(:warn, message, state)
end
if mode == :discard do
log(:warn, "Logger has stopped discarding messages", state)
end
state = persist(%{state | mode: new_mode})
{:ok, {state, thresholds}}
end
{:ok, {compute_mode_and_persist_state(state, thresholds), thresholds}}
end
def handle_call({:configure, options}, {%{mode: mode}, _}) do
@@ -127,6 +106,16 @@ defmodule Logger.Config do
{:ok, old, state}
end
def handle_info(@check_discard, {%{mode: :discard} = state, thresholds}) do
state = compute_mode_and_persist_state(state, thresholds)
if state.mode == :discard do
Process.send_after(self(), @check_discard, state.discard_threshold_periodic_check)
end
{:ok, {state, thresholds}}
end
def handle_info(_msg, state) do
{:ok, state}
end
@@ -139,6 +128,31 @@ defmodule Logger.Config do
{:ok, state}
end
defp compute_mode_and_persist_state(%{mode: mode} = state, thresholds) do
case compute_mode(mode, thresholds) do
^mode ->
state
new_mode ->
if new_mode == :discard do
Process.send_after(self(), @check_discard, state.discard_threshold_periodic_check)
message =
"Logger has #{message_queue_length()} messages in its queue, " <>
"which is above :discard_threshold. Messages will be discarded " <>
"until the message queue goes back to 75% of the threshold size"
log(:warn, message, state)
end
if mode == :discard do
log(:warn, "Logger has stopped discarding messages", state)
end
persist(%{state | mode: new_mode})
end
end
## Helpers
defp log(level, message, state) do
@@ -163,7 +177,9 @@ defmodule Logger.Config do
level: Application.get_env(:logger, :level),
translators: Application.get_env(:logger, :translators),
truncate: Application.get_env(:logger, :truncate),
utc_log: Application.get_env(:logger, :utc_log)
utc_log: Application.get_env(:logger, :utc_log),
discard_threshold_periodic_check:
Application.get_env(:logger, :discard_threshold_periodic_check)
})
end
+13
View File
@@ -126,6 +126,19 @@ defmodule Logger.Translator do
{:ok, msg, metadata}
{'Error in process ' ++ _, [pid, node, {reason, stack}]} ->
reason = Exception.normalize(:error, reason, stack)
msg = [
"Process ",
inspect(pid),
" on node ",
inspect(node),
" raised an exception" | format(:error, reason, stack)
]
{:ok, msg, [crash_reason: exit_reason(:error, reason, stack)]}
{'Error in process ' ++ _, [pid, {reason, stack}]} ->
reason = Exception.normalize(:error, reason, stack)
msg = ["Process ", inspect(pid), " raised an exception" | format(:error, reason, stack)]
+1
View File
@@ -24,6 +24,7 @@ defmodule Logger.MixProject do
handle_otp_reports: true,
handle_sasl_reports: false,
discard_threshold_for_error_logger: 500,
discard_threshold_periodic_check: 30_000,
compile_time_purge_level: :debug,
compile_time_purge_matching: [],
compile_time_application: nil,
+26 -1
View File
@@ -12,11 +12,29 @@ defmodule Logger.ConfigTest do
end
test "log/2 relies on discard_threshold" do
Logger.remove_backend(:console)
Logger.configure(discard_threshold: 0)
for _ <- 1..1000, do: Logger.log(:info, "some message")
after
Logger.configure(discard_threshold: 10000)
end
test "log/2 recovers from discard_threshold" do
Logger.remove_backend(:console)
Logger.configure(discard_threshold: 1, discard_threshold_periodic_check: 0)
:sys.suspend(Logger)
assert Logger.log(:info, "BEGIN") == :ok
send(Logger, :garbage)
:sys.resume(Logger)
# Simulate a sync operation to make sure we are in :discard mode
Logger.add_translator({Unknown, :translate})
Logger.remove_translator({Unknown, :translate})
# It should eventually heal
wait_for_mode(:async)
after
Logger.configure(discard_threshold: 10000, discard_threshold_periodic_check: 30000)
Logger.add_backend(:console)
end
@@ -37,4 +55,11 @@ defmodule Logger.ConfigTest do
Logger.configure(level: :debug)
end
end
def wait_for_mode(mode) do
unless Logger.Config.__data__().mode == mode do
Process.sleep(10)
wait_for_mode(mode)
end
end
end
@@ -981,6 +981,19 @@ defmodule Logger.TranslatorTest do
assert {:stop, [_ | _]} = process_metadata[:crash_reason]
end
test "translates process crash with erts" do
assert {:ok, msg, meta} =
Logger.Translator.translate(
:error,
:error,
:format,
{'Error in process ~p on node ~p with exit value:~n~p~n',
[self(), :"name@127.0.0.1", {:badarith, [{:erlang, :/, [1, 0], []}]}]}
)
assert Keyword.get(meta, :crash_reason)
end
test "reports :undefined MFA properly" do
defmodule WeirdFunctionNamesGenServer do
use GenServer
+9 -4
View File
@@ -132,13 +132,18 @@ defmodule Mix.Dep do
for dep <- deps,
dep.app == app,
child <- dep.deps,
do: {child.app, true},
do: {child.app, Keyword.get(child.opts, :optional, false)},
into: %{}
Enum.map(children, fn %{app: app} = dep ->
Enum.map(children, fn %{app: app, opts: opts} = dep ->
# optional only matters at the top level. Any non-top level dependency
# that is optional and is still available means it has been fulfilled.
case top_level do
%{^app => _} -> %{dep | top_level: true}
%{} -> %{dep | top_level: false}
%{^app => optional} ->
%{dep | top_level: true, opts: Keyword.put(opts, :optional, optional)}
%{} ->
%{dep | top_level: false, opts: Keyword.delete(opts, :optional)}
end
end)
end
+4 -3
View File
@@ -212,7 +212,7 @@ defmodule Mix.Tasks.Compile.App do
apps =
properties
|> Keyword.get(:applications)
|> Kernel.||(apps_from_prod_deps(properties, config))
|> Kernel.||(apps_from_prod_non_optional_deps(properties, config))
|> normalize_apps(extra, config)
Keyword.put(properties, :applications, apps)
@@ -317,11 +317,12 @@ defmodule Mix.Tasks.Compile.App do
end)
end
defp apps_from_prod_deps(properties, config) do
defp apps_from_prod_non_optional_deps(properties, config) do
included_applications = Keyword.get(properties, :included_applications, [])
non_runtime_deps = non_runtime_deps(config)
for %{app: app, top_level: true} <- Mix.Dep.cached(),
for %{app: app, opts: opts, top_level: true} <- Mix.Dep.cached(),
not Keyword.get(opts, :optional, false),
not Map.has_key?(non_runtime_deps, app),
app not in included_applications,
do: app
+25
View File
@@ -196,6 +196,31 @@ defmodule Mix.DepTest do
end)
end
test "nested deps with optional matching" do
Process.put(:custom_deps_git_repo_opts, optional: true)
# deps_repo brings git_repo but it is optional
deps = [
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo")}
]
with_deps(deps, fn ->
in_fixture("deps_status", fn ->
File.mkdir_p!("custom/deps_repo/lib")
File.write!("custom/deps_repo/lib/a.ex", """
# Check that the child dependency is top_level and optional
[%Mix.Dep{app: :git_repo, top_level: true, opts: opts}] = Mix.Dep.cached()
true = Keyword.fetch!(opts, :optional)
""")
Mix.Tasks.Deps.Get.run([])
Mix.Tasks.Deps.Compile.run([])
end)
end)
end
test "nested deps with convergence and optional dependencies" do
deps = [
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
+1 -1
View File
@@ -99,7 +99,7 @@ defmodule Mix.Tasks.Compile.AppTest do
properties = parse_resource_file(:custom_deps)
assert properties[:applications] ==
[:kernel, :stdlib, :elixir, :logger, :ok1, :ok3, :ok4, :ok6, :ok7]
[:kernel, :stdlib, :elixir, :logger, :ok1, :ok3, :ok4, :ok7]
end)
end