Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffe7a577cc | ||
|
|
a2f14bd007 | ||
|
|
66ac6a3d8a | ||
|
|
50caa25d41 | ||
|
|
92af3fdf0f | ||
|
|
c443cdee36 | ||
|
|
8ca3876b10 | ||
|
|
c7e822345b | ||
|
|
b43a6a923e | ||
|
|
e60fe36740 | ||
|
|
f5735eb697 | ||
|
|
7002554a47 | ||
|
|
660a09b3af | ||
|
|
79388035f5 | ||
|
|
edc204f0b2 | ||
|
|
7d2cee20f6 | ||
|
|
a58a924e10 | ||
|
|
e0a9b4b476 | ||
|
|
eb8121c790 | ||
|
|
3f0608bdc3 | ||
|
|
9797a466fc | ||
|
|
418c277dfb | ||
|
|
580bd764f7 | ||
|
|
49dec48926 | ||
|
|
c8c7663c83 | ||
|
|
4e6261a392 | ||
|
|
f10cf8bdc8 | ||
|
|
75313ababc | ||
|
|
570d44b502 |
@@ -58,6 +58,52 @@ Another important change related to configuration is that `mix new` will no long
|
||||
|
||||
There are many other enhancements. The Elixir CLI got a handful of new options in order to best support releases. `Logger` now computes its sync/async/discard thresholds in a decentralized fashion, reducing contention. `EEx` templates support more complex expressions than before. Finally, there is a new `~U` sigil for working with UTC DateTimes as well as new functions in the `File`, `Registry`, and `System` modules.
|
||||
|
||||
## v1.9.2 (2019-10-12)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix release] Allow `{:from_app, app_name}` as a version for releases
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Ensure compilation works for a variable named `super`
|
||||
* [Kernel] Ensure capture operator of a local function expands correctly inside a macro
|
||||
* [Regex] Ensure dynamic recompilation of regexes considers options. This fixes an issue where parsing the protocol in `URI.parse/1` seemingly looked case sensitive when running Elixir precompiled on another machine
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix release] Use `Base.encode32` when generating cookie to avoid unsafe chars
|
||||
* [mix release] Fix `install` command on Windows
|
||||
* [mix release] Quote executable path on Windows to ensure it works on directories with spaces
|
||||
|
||||
## v1.9.1 (2019-07-18)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix format] Print relative paths in `--check-formatted` output
|
||||
* [mix release] Support included applications
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Fix formatter wrongly removing nested parens in nested calls
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Do not crash translator on poorly formatted supervisor names
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Raise readable error for mismatched sources during compilation
|
||||
* [mix release] Preserve UTF8 encoding in release config files
|
||||
|
||||
## v1.9.0 (2019-06-24)
|
||||
|
||||
### 1. Enhancements
|
||||
@@ -135,6 +181,7 @@ There are many other enhancements. The Elixir CLI got a handful of new options i
|
||||
* [Kernel] Type variables starting with an underscore (`_foo`) should not raise compile error
|
||||
* [Kernel] Keep order of elements when macro `in/2` is expanded with a literal list on the right-hand side
|
||||
* [Kernel] Print proper location on undefined function error from dynamically generated functions
|
||||
* [Kernel] **Potentially breaking** Do not leak aliases when nesting module definitions that are fully namespaced modules. If you defined `defmodule Elixir.Foo.Bar` inside `defmodule Foo`, previous Elixir versions would automatically define an alias, but fully namespaced modules such as `Elixir.Foo.Bar` should never define or require an alias. If you were accidentally relying on this broken behaviour, your code may no longer work
|
||||
* [System] Make sure `:init.get_status/0` is set to `{:started, :started}` once the system starts
|
||||
* [Path] Do not expand `~` in `Path.expand/2` when not followed by a path separator
|
||||
* [Protocol] Ensure `debug_info` is kept in protocols
|
||||
|
||||
+2
-2
@@ -156,9 +156,9 @@ if not !runMode! == "iex" (
|
||||
set beforeExtra=-noshell -s elixir start_cli !beforeExtra!
|
||||
)
|
||||
if defined useWerl (
|
||||
start !ERTS_BIN!werl.exe !ext_libs! !ELIXIR_ERL_OPTIONS! !parsErlang! !beforeExtra! -extra !parsElixir!
|
||||
start "" "!ERTS_BIN!werl.exe" !ext_libs! !ELIXIR_ERL_OPTIONS! !parsErlang! !beforeExtra! -extra !parsElixir!
|
||||
) else (
|
||||
!ERTS_BIN!erl.exe !ext_libs! !ELIXIR_ERL_OPTIONS! !parsErlang! !beforeExtra! -extra !parsElixir!
|
||||
"!ERTS_BIN!erl.exe" !ext_libs! !ELIXIR_ERL_OPTIONS! !parsErlang! !beforeExtra! -extra !parsElixir!
|
||||
)
|
||||
:end
|
||||
endlocal
|
||||
@@ -123,7 +123,6 @@ defmodule Agent do
|
||||
The generated `child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the child should be restarted, defaults to `:permanent`
|
||||
* `:shutdown` - how to shut down the child, either immediately or by giving it time to shut down
|
||||
|
||||
|
||||
@@ -1075,7 +1075,7 @@ defmodule Code.Formatter do
|
||||
defp local_to_algebra(fun, meta, args, context, state) when is_atom(fun) do
|
||||
skip_parens =
|
||||
cond do
|
||||
not Keyword.get(meta, :no_parens, false) -> :required
|
||||
not Keyword.get(meta, :no_parens, false) -> :skip_if_only_do_end
|
||||
local_without_parens?(fun, args, state) -> :skip_unless_many_args
|
||||
true -> :skip_if_do_end
|
||||
end
|
||||
@@ -1093,9 +1093,10 @@ defmodule Code.Formatter do
|
||||
{doc, state}
|
||||
end
|
||||
|
||||
# parens may one of:
|
||||
# parens may be one of:
|
||||
#
|
||||
# * :skip_unless_many_args - skips parens unless we are the argument context
|
||||
# * :skip_if_only_do_end - skip parens if we are do-end and the only arg
|
||||
# * :skip_if_do_end - skip parens if we are do-end
|
||||
# * :required - never skip parens
|
||||
#
|
||||
@@ -1111,11 +1112,16 @@ defmodule Code.Formatter do
|
||||
|
||||
if blocks = do_end_blocks(last, state) do
|
||||
{call_doc, state} =
|
||||
if rest == [] do
|
||||
{" do", state}
|
||||
else
|
||||
no_parens? = parens != :required
|
||||
call_args_to_algebra_no_blocks(meta, rest, no_parens?, list_to_keyword?, " do", state)
|
||||
case rest do
|
||||
[] when parens == :required ->
|
||||
{"() do", state}
|
||||
|
||||
[] ->
|
||||
{" do", state}
|
||||
|
||||
_ ->
|
||||
no_parens? = parens not in [:required, :skip_if_only_do_end]
|
||||
call_args_to_algebra_no_blocks(meta, rest, no_parens?, list_to_keyword?, " do", state)
|
||||
end
|
||||
|
||||
{blocks_doc, state} = do_end_blocks_to_algebra(blocks, state)
|
||||
|
||||
@@ -55,7 +55,7 @@ defmodule Config do
|
||||
|
||||
It has to be replaced by:
|
||||
|
||||
for config <- "apps/*/config/config.exs" |> Path.expand() |> Path.wildcard() do
|
||||
for config <- "../apps/*/config/config.exs" |> Path.expand(__DIR__) |> Path.wildcard() do
|
||||
import_config config
|
||||
end
|
||||
|
||||
|
||||
@@ -220,9 +220,9 @@ defmodule Config.Provider do
|
||||
end
|
||||
|
||||
defp write_config!(config, path) do
|
||||
contents = :io_lib.format("%% coding: utf-8~n~p.~n", [config])
|
||||
contents = :io_lib.format("%% coding: utf-8~n~tw.~n", [config])
|
||||
|
||||
case File.write(path, contents) do
|
||||
case File.write(path, contents, [:utf8]) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@ defmodule Exception do
|
||||
{_, kind, _, clauses} <- List.keyfind(defs, {function, arity}, 0) do
|
||||
clauses =
|
||||
for {meta, ex_args, guards, _block} <- clauses do
|
||||
scope = :elixir_erl.scope(meta)
|
||||
scope = :elixir_erl.scope(meta, true)
|
||||
|
||||
{erl_args, scope} =
|
||||
:elixir_erl_clauses.match(&:elixir_erl_pass.translate_args/2, ex_args, scope)
|
||||
|
||||
@@ -142,7 +142,6 @@ defmodule GenServer do
|
||||
The generated `child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the child should be restarted, defaults to `:permanent`
|
||||
* `:shutdown` - how to shut down the child, either immediately or by giving it time to shut down
|
||||
|
||||
|
||||
@@ -4433,14 +4433,18 @@ defmodule Kernel do
|
||||
defoverridable message: 1
|
||||
|
||||
@impl true
|
||||
def exception(msg) when is_binary(msg) do
|
||||
def exception(msg) when Kernel.is_binary(msg) do
|
||||
exception(message: msg)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Change the implementation on v2.0 to simply call Kernel.struct!/2
|
||||
# Calls to Kernel functions must be fully-qualified to ensure
|
||||
# reproducible builds; otherwise, this macro will generate ASTs
|
||||
# with different metadata (:import, :context) depending on if
|
||||
# it is the bootstrapped version or not.
|
||||
@impl true
|
||||
def exception(args) when is_list(args) do
|
||||
def exception(args) when Kernel.is_list(args) do
|
||||
struct = __struct__()
|
||||
{valid, invalid} = Enum.split_with(args, fn {k, _} -> Map.has_key?(struct, k) end)
|
||||
|
||||
@@ -4451,9 +4455,9 @@ defmodule Kernel do
|
||||
_ ->
|
||||
IO.warn(
|
||||
"the following fields are unknown when raising " <>
|
||||
"#{inspect(__MODULE__)}: #{inspect(invalid)}. " <>
|
||||
"#{Kernel.inspect(__MODULE__)}: #{Kernel.inspect(invalid)}. " <>
|
||||
"Please make sure to only give known fields when raising " <>
|
||||
"or redefine #{inspect(__MODULE__)}.exception/1 to " <>
|
||||
"or redefine #{Kernel.inspect(__MODULE__)}.exception/1 to " <>
|
||||
"discard unknown fields. Future Elixir versions will raise on " <>
|
||||
"unknown fields given to raise/2"
|
||||
)
|
||||
|
||||
@@ -461,13 +461,13 @@ defmodule Regex do
|
||||
end
|
||||
|
||||
defp safe_run(
|
||||
%Regex{re_pattern: compiled, source: source, re_version: version},
|
||||
%Regex{re_pattern: compiled, source: source, re_version: version, opts: compile_opts},
|
||||
string,
|
||||
options
|
||||
) do
|
||||
case version() do
|
||||
^version -> :re.run(string, compiled, options)
|
||||
_ -> :re.run(string, source, options)
|
||||
_ -> :re.run(string, source, translate_options(compile_opts, options))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -306,7 +306,6 @@ defmodule Supervisor do
|
||||
following options:
|
||||
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the supervisor should be restarted, defaults to `:permanent`
|
||||
|
||||
The `@doc` annotation immediately preceding `use Supervisor` will be
|
||||
|
||||
@@ -910,10 +910,14 @@ defmodule System do
|
||||
`convert_time_unit/3` accepts an additional time unit (other than the
|
||||
ones in the `t:time_unit/0` type) called `:native`. `:native` is the time
|
||||
unit used by the Erlang runtime system. It's determined when the runtime
|
||||
starts and stays the same until the runtime is stopped. To determine what
|
||||
the `:native` unit amounts to in a system, you can call this function to
|
||||
convert 1 second to the `:native` time unit (i.e.,
|
||||
`System.convert_time_unit(1, :second, :native)`).
|
||||
starts and stays the same until the runtime is stopped, but could differ
|
||||
the next time the runtime is started on the same machine. For this reason,
|
||||
you should use this function to convert `:native` time units to a predictable
|
||||
unit before you display them to humans.
|
||||
|
||||
To determine how many seconds the `:native` unit represents in your current
|
||||
runtime, you can can call this function to convert 1 second to the `:native`
|
||||
time unit: `System.convert_time_unit(1, :second, :native)`.
|
||||
"""
|
||||
@spec convert_time_unit(integer, time_unit | :native, time_unit | :native) :: integer
|
||||
def convert_time_unit(time, from_unit, to_unit) do
|
||||
|
||||
@@ -97,7 +97,6 @@ defmodule Task do
|
||||
`child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* `:start` - how to start the child process (defaults to calling `__MODULE__.start_link/1`)
|
||||
* `:restart` - when the child should be restarted, defaults to `:temporary`
|
||||
* `:shutdown` - how to shut down the child, either immediately or by giving it time to shut down
|
||||
|
||||
|
||||
@@ -31,16 +31,17 @@ defmodule Tuple do
|
||||
|
||||
The functions in this module that add and remove elements from tuples are
|
||||
rarely used in practice, as they typically imply tuples are being used as
|
||||
collections. To append to a tuple, it is preferable to use pattern matching:
|
||||
collections. To append to a tuple, it is preferable to extract the elements
|
||||
from the old tuple with pattern matching, and then create a new tuple:
|
||||
|
||||
tuple = {:ok, :example}
|
||||
|
||||
# Avoid
|
||||
Tuple.insert_at(tuple, 2, %{})
|
||||
result = Tuple.insert_at(tuple, 2, %{})
|
||||
|
||||
# Prefer
|
||||
{:ok, atom} = tuple
|
||||
{:ok, atom, %{}}
|
||||
result = {:ok, atom, %{}}
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
backup_vars=nil, %% a copy of vars to be used on ^var
|
||||
extra_guards=[], %% extra guards from args expansion
|
||||
counter=#{}, %% a map counting the variables defined
|
||||
expand_captures=false, %% a boolean to control if captures should be expanded
|
||||
stacktrace=false %% holds information about the stacktrace variable
|
||||
}).
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%% Compiler backend to Erlang.
|
||||
-module(elixir_erl).
|
||||
-export([elixir_to_erl/1, definition_to_anonymous/4, compile/1, consolidate/3,
|
||||
get_ann/1, debug_info/4, scope/1, format_error/1]).
|
||||
get_ann/1, debug_info/4, scope/2, format_error/1]).
|
||||
-include("elixir.hrl").
|
||||
-define(typespecs, 'Elixir.Kernel.Typespec').
|
||||
|
||||
@@ -48,7 +48,7 @@ get_ann([], Gen, Line) -> erl_anno:set_generated(Gen, Line).
|
||||
%% Converts an Elixir definition to an anonymous function.
|
||||
|
||||
definition_to_anonymous(Module, Kind, Meta, Clauses) ->
|
||||
ErlClauses = [translate_clause(Kind, Clause) || Clause <- Clauses],
|
||||
ErlClauses = [translate_clause(Kind, Clause, true) || Clause <- Clauses],
|
||||
Fun = {'fun', ?ann(Meta), {clauses, ErlClauses}},
|
||||
LocalHandler = fun(LocalName, LocalArgs) -> invoke_local(Module, LocalName, LocalArgs) end,
|
||||
{value, Result, _Binding} = erl_eval:expr(Fun, [], {value, LocalHandler}),
|
||||
@@ -117,8 +117,8 @@ elixir_to_erl_cons(T) -> elixir_to_erl(T).
|
||||
|
||||
%% Returns a scope for translation.
|
||||
|
||||
scope(_Meta) ->
|
||||
#elixir_erl{}.
|
||||
scope(_Meta, ExpandCaptures) ->
|
||||
#elixir_erl{expand_captures=ExpandCaptures}.
|
||||
|
||||
%% Static compilation hook, used in protocol consolidation
|
||||
|
||||
@@ -220,15 +220,15 @@ add_definition(Meta, Body, {Head, Tail}) ->
|
||||
end.
|
||||
|
||||
translate_definition(Kind, Meta, {Name, Arity}, Clauses) ->
|
||||
ErlClauses = [translate_clause(Kind, Clause) || Clause <- Clauses],
|
||||
ErlClauses = [translate_clause(Kind, Clause, false) || Clause <- Clauses],
|
||||
|
||||
case is_macro(Kind) of
|
||||
true -> {function, ?ann(Meta), elixir_utils:macro_name(Name), Arity + 1, ErlClauses};
|
||||
false -> {function, ?ann(Meta), Name, Arity, ErlClauses}
|
||||
end.
|
||||
|
||||
translate_clause(Kind, {Meta, Args, Guards, Body}) ->
|
||||
S = scope(Meta),
|
||||
translate_clause(Kind, {Meta, Args, Guards, Body}, ExpandCaptures) ->
|
||||
S = scope(Meta, ExpandCaptures),
|
||||
|
||||
{TClause, TS} = elixir_erl_clauses:clause(Meta,
|
||||
fun elixir_erl_pass:translate_args/2, Args, Body, Guards, S),
|
||||
|
||||
@@ -69,7 +69,7 @@ translate({'__CALLER__', Meta, Atom}, S) when is_atom(Atom) ->
|
||||
translate({'__STACKTRACE__', Meta, Atom}, S = #elixir_erl{stacktrace={Var, _}}) when is_atom(Atom) ->
|
||||
{{var, ?ann(Meta), Var}, S#elixir_erl{stacktrace={Var, true}}};
|
||||
|
||||
translate({'super', Meta, Args}, S) ->
|
||||
translate({'super', Meta, Args}, S) when is_list(Args) ->
|
||||
%% In the expanded AST, super is used to invoke a function
|
||||
%% in the current module originated from a default clause
|
||||
%% or a super call.
|
||||
@@ -96,7 +96,15 @@ translate({'&', Meta, [{'/', _, [{{'.', _, [Remote, Fun]}, _, []}, Arity]}]}, S)
|
||||
{{'fun', Ann, {function, TRemote, TFun, TArity}}, SR};
|
||||
translate({'&', Meta, [{'/', _, [{Fun, _, Atom}, Arity]}]}, S)
|
||||
when is_atom(Fun), is_atom(Atom), is_integer(Arity) ->
|
||||
{{'fun', ?ann(Meta), {function, Fun, Arity}}, S};
|
||||
case S of
|
||||
#elixir_erl{expand_captures=true} ->
|
||||
Vars = [{list_to_atom("arg" ++ integer_to_list(Counter)), [], ?MODULE}
|
||||
|| Counter <- tl(lists:seq(0, Arity))],
|
||||
translate({'fn', Meta, [{'->', Meta, [Vars, {Fun, Meta, Vars}]}]}, S);
|
||||
|
||||
#elixir_erl{expand_captures=false} ->
|
||||
{{'fun', ?ann(Meta), {function, Fun, Arity}}, S}
|
||||
end;
|
||||
|
||||
translate({fn, Meta, Clauses}, S) ->
|
||||
Transformer = fun({'->', CMeta, [ArgsWithGuards, Expr]}, Acc) ->
|
||||
|
||||
@@ -10,14 +10,21 @@
|
||||
|
||||
-include("elixir.hrl").
|
||||
-define(cache, {elixir, cache_env}).
|
||||
-define(locals, {elixir, locals}).
|
||||
-define(tracker, 'Elixir.Module.LocalsTracker').
|
||||
|
||||
setup({DataSet, _DataBag}) ->
|
||||
ets:insert(DataSet, {?cache, 0, nil}),
|
||||
|
||||
case elixir_config:get(bootstrap) of
|
||||
false -> ets:insert(DataSet, {?locals, true});
|
||||
true -> ok
|
||||
end,
|
||||
|
||||
ok.
|
||||
|
||||
stop({_DataSet, _DataBag}) ->
|
||||
ok.
|
||||
stop({DataSet, _DataBag}) ->
|
||||
ets:delete(DataSet, ?locals).
|
||||
|
||||
yank(Tuple, Module) ->
|
||||
if_tracker(Module, fun(Tracker) -> ?tracker:yank(Tracker, Tuple) end).
|
||||
@@ -44,9 +51,12 @@ if_tracker(Module, Callback) ->
|
||||
if_tracker(Module, ok, Callback).
|
||||
|
||||
if_tracker(Module, Default, Callback) ->
|
||||
try {elixir_config:get(bootstrap), elixir_module:data_tables(Module)} of
|
||||
{true, _Tracker} -> Default;
|
||||
{false, Tracker} -> Callback(Tracker)
|
||||
try
|
||||
{DataSet, _} = Tables = elixir_module:data_tables(Module),
|
||||
{ets:member(DataSet, ?locals), Tables}
|
||||
of
|
||||
{true, Tracker} -> Callback(Tracker);
|
||||
{false, _} -> Default
|
||||
catch
|
||||
error:badarg -> Default
|
||||
end.
|
||||
|
||||
@@ -449,6 +449,12 @@ defmodule Code.Formatter.CallsTest do
|
||||
assert_same "unquote(call)()"
|
||||
assert_same "unquote(call)(one, two)"
|
||||
|
||||
assert_same """
|
||||
unquote(call)() do
|
||||
:ok
|
||||
end
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
unquote(call)(one, two) do
|
||||
:ok
|
||||
@@ -668,6 +674,11 @@ defmodule Code.Formatter.CallsTest do
|
||||
assert_same "foo.bar(call)()"
|
||||
assert_same "foo.bar(call)(one, two)"
|
||||
|
||||
assert_same """
|
||||
foo.bar(call)() do
|
||||
end
|
||||
"""
|
||||
|
||||
assert_same """
|
||||
foo.bar(call)(one, two) do
|
||||
:ok
|
||||
|
||||
@@ -18,7 +18,7 @@ defmodule Config.ProviderTest do
|
||||
|
||||
File.rm_rf(@tmp_path)
|
||||
File.mkdir_p!(@tmp_path)
|
||||
File.write!(@sys_config, :io_lib.format("~p.~n", [context[:sys_config] || []]))
|
||||
File.write!(@sys_config, :io_lib.format("~tw.~n", [context[:sys_config] || []]), [:utf8])
|
||||
|
||||
on_exit(fn ->
|
||||
Application.delete_env(@config_app, :config_providers)
|
||||
@@ -50,14 +50,16 @@ defmodule Config.ProviderTest do
|
||||
assert config[@config_app] == [config_providers: :booted]
|
||||
end
|
||||
|
||||
test "writes extra config" do
|
||||
init_and_assert_boot(extra_config: [my_app: [key: :old_value, sys_key: :sys_value]])
|
||||
assert consult(@sys_config)[:my_app] == [sys_key: :sys_value, key: :value]
|
||||
@tag sys_config: [my_app: [encoding: {:"£", "£", '£'}]]
|
||||
test "writes sys_config with encoding" do
|
||||
init_and_assert_boot()
|
||||
config = consult(@sys_config)
|
||||
assert config[:my_app][:encoding] == {:"£", "£", '£'}
|
||||
end
|
||||
|
||||
@tag sys_config: [my_app: [key: :old_value, sys_key: :sys_value, extra_config: :old_value]]
|
||||
test "overrides sys_config" do
|
||||
init_and_assert_boot(extra_config: [my_app: [extra_config: :value]])
|
||||
test "writes extra config with overrides" do
|
||||
init_and_assert_boot(extra_config: [my_app: [key: :old_extra_value, extra_config: :value]])
|
||||
|
||||
assert consult(@sys_config)[:my_app] ==
|
||||
[sys_key: :sys_value, extra_config: :value, key: :value]
|
||||
|
||||
@@ -34,6 +34,10 @@ defmodule Kernel.MacrosTest do
|
||||
quote(do: 1 + unquote(value))
|
||||
end
|
||||
|
||||
defmacro my_macro_with_capture(value) do
|
||||
Enum.map(value, &by_two/1)
|
||||
end
|
||||
|
||||
test "require" do
|
||||
assert Kernel.MacrosTest.Nested.value() == 1
|
||||
end
|
||||
@@ -42,7 +46,7 @@ defmodule Kernel.MacrosTest do
|
||||
assert Nested.value() == 1
|
||||
end
|
||||
|
||||
test "local but private macro" do
|
||||
test "local with private macro" do
|
||||
assert my_private_macro() == 4
|
||||
end
|
||||
|
||||
@@ -54,6 +58,10 @@ defmodule Kernel.MacrosTest do
|
||||
assert my_macro_with_local(4) == 17
|
||||
end
|
||||
|
||||
test "local with capture" do
|
||||
assert my_macro_with_capture([1, 2, 3]) == [2, 4, 6]
|
||||
end
|
||||
|
||||
test "macros cannot be called dynamically" do
|
||||
x = Nested
|
||||
assert_raise UndefinedFunctionError, fn -> x.value end
|
||||
|
||||
@@ -254,6 +254,11 @@ defmodule Kernel.OverridableTest do
|
||||
assert Overridable.capture_super_macro(5) == [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
|
||||
end
|
||||
|
||||
test "super as a variable" do
|
||||
super = :ok
|
||||
assert super == :ok
|
||||
end
|
||||
|
||||
test "overridable with many clauses" do
|
||||
assert Overridable.many_clauses(0) == 11
|
||||
assert Overridable.many_clauses(1) == 13
|
||||
|
||||
@@ -5,7 +5,7 @@ defmodule NodeTest do
|
||||
|
||||
doctest Node
|
||||
|
||||
@tag :unix
|
||||
@tag :epmd
|
||||
test "start/3 and stop/0" do
|
||||
assert Node.stop() == {:error, :not_found}
|
||||
assert {:ok, _} = Node.start(:hello, :shortnames, 15000)
|
||||
|
||||
@@ -194,6 +194,10 @@ defmodule RegexTest do
|
||||
assert Regex.run(@re_19_3_little, "abcd abce", capture: :all_names) == ["d"]
|
||||
end
|
||||
|
||||
test "run/3 with regexes with options compiled in different systems" do
|
||||
assert Regex.run(%{~r/foo/i | re_version: "bad version"}, "FOO") == ["FOO"]
|
||||
end
|
||||
|
||||
test "scan/2" do
|
||||
assert Regex.scan(~r"c(d|e)", "abcd abce") == [["cd", "d"], ["ce", "e"]]
|
||||
assert Regex.scan(~r"c(?:d|e)", "abcd abce") == [["cd"], ["ce"]]
|
||||
@@ -213,6 +217,10 @@ defmodule RegexTest do
|
||||
assert Regex.scan(@re_19_3_little, "abcd abce", capture: :all_names) == [["d"], ["e"]]
|
||||
end
|
||||
|
||||
test "scan/2 with regexes with options compiled in different systems" do
|
||||
assert Regex.scan(%{~r/foo/i | re_version: "bad version"}, "FOO") == [["FOO"]]
|
||||
end
|
||||
|
||||
test "split/2,3" do
|
||||
assert Regex.split(~r",", "") == [""]
|
||||
assert Regex.split(~r",", "", trim: true) == []
|
||||
|
||||
@@ -84,10 +84,11 @@ defmodule CodeFormatterHelpers do
|
||||
end
|
||||
|
||||
assert_timeout = String.to_integer(System.get_env("ELIXIR_ASSERT_TIMEOUT") || "500")
|
||||
exclude = if PathHelpers.windows?(), do: [unix: true], else: [windows: true]
|
||||
epmd_exclude = if match?({_, 0}, System.cmd("epmd", ["-daemon"])), do: [], else: [epmd: true]
|
||||
os_exclude = if PathHelpers.windows?(), do: [unix: true], else: [windows: true]
|
||||
|
||||
ExUnit.start(
|
||||
trace: "--trace" in System.argv(),
|
||||
assert_receive_timeout: assert_timeout,
|
||||
exclude: exclude
|
||||
exclude: epmd_exclude ++ os_exclude
|
||||
)
|
||||
|
||||
@@ -332,6 +332,7 @@ defmodule Logger.Translator do
|
||||
defp sup_name({:global, name}), do: inspect(name)
|
||||
defp sup_name({:via, _mod, name}), do: inspect(name)
|
||||
defp sup_name({pid, mod}), do: [inspect(pid), " (", inspect(mod), ?)]
|
||||
defp sup_name(unknown_name), do: inspect(unknown_name)
|
||||
|
||||
defp sup_context(:start_error), do: "failed to start"
|
||||
defp sup_context(:child_terminated), do: "terminated"
|
||||
|
||||
@@ -264,7 +264,12 @@ defmodule Mix.Compilers.Elixir do
|
||||
nil -> {[file], false}
|
||||
end
|
||||
|
||||
{source, sources} = List.keytake(sources, file, source(:source))
|
||||
{source, sources} =
|
||||
List.keytake(sources, file, source(:source)) ||
|
||||
Mix.raise(
|
||||
"Could not find source for #{inspect(file)}. Make sure the :elixirc_paths configuration " <>
|
||||
"is a list of relative paths to the current project or absolute paths to external directories"
|
||||
)
|
||||
|
||||
source =
|
||||
source(
|
||||
@@ -515,10 +520,17 @@ defmodule Mix.Compilers.Elixir do
|
||||
split_manifest(data, compile_path)
|
||||
|
||||
[v | data] when is_integer(v) ->
|
||||
for module <- data, is_record(module, :module) do
|
||||
File.rm(Path.join(compile_path, module(module, :beam)))
|
||||
:code.purge(module(module, :module))
|
||||
:code.delete(module(module, :module))
|
||||
try do
|
||||
for module <- data, is_record(module, :module) do
|
||||
File.rm(Path.join(compile_path, module(module, :beam)))
|
||||
:code.purge(module(module, :module))
|
||||
:code.delete(module(module, :module))
|
||||
end
|
||||
rescue
|
||||
_ ->
|
||||
Mix.raise(
|
||||
"Cannot clean-up stale manifest, please run \"mix clean --deps\" manually before proceeding"
|
||||
)
|
||||
end
|
||||
|
||||
{[], []}
|
||||
|
||||
+72
-23
@@ -7,7 +7,8 @@ defmodule Mix.Release do
|
||||
The Mix.Release struct has the following read-only fields:
|
||||
|
||||
* `:name` - the name of the release as an atom
|
||||
* `:version` - the version of the release as a string
|
||||
* `:version` - the version of the release as a string or
|
||||
`{:from_app, app_name}
|
||||
* `:path` - the path to the release root
|
||||
* `:version_path` - the path to the release version inside the release
|
||||
* `:applications` - a map of application with their definitions
|
||||
@@ -47,7 +48,7 @@ defmodule Mix.Release do
|
||||
name: atom(),
|
||||
version: String.t(),
|
||||
path: String.t(),
|
||||
version_path: String.t(),
|
||||
version_path: String.t() | {:from_app, application()},
|
||||
applications: %{application() => keyword()},
|
||||
boot_scripts: %{atom() => [{application(), mode()}]},
|
||||
erts_version: charlist(),
|
||||
@@ -83,14 +84,14 @@ defmodule Mix.Release do
|
||||
{include_erts, opts} = Keyword.pop(opts, :include_erts, true)
|
||||
{erts_source, erts_lib_dir, erts_version} = erts_data(include_erts)
|
||||
|
||||
loaded_apps = apps |> Keyword.keys() |> load_apps(%{}, erts_lib_dir)
|
||||
loaded_apps = apps |> Keyword.keys() |> load_apps(%{}, erts_lib_dir, :maybe)
|
||||
|
||||
# Make sure IEx is either an active part of the release or add it as none.
|
||||
{loaded_apps, apps} =
|
||||
if Map.has_key?(loaded_apps, :iex) do
|
||||
{loaded_apps, apps}
|
||||
else
|
||||
{load_apps([:iex], loaded_apps, erts_lib_dir), apps ++ [iex: :none]}
|
||||
{load_apps([:iex], loaded_apps, erts_lib_dir, :maybe), apps ++ [iex: :none]}
|
||||
end
|
||||
|
||||
start_boot = build_start_boot(loaded_apps, apps)
|
||||
@@ -112,6 +113,24 @@ defmodule Mix.Release do
|
||||
)
|
||||
end)
|
||||
|
||||
version =
|
||||
case version do
|
||||
{:from_app, app} ->
|
||||
Application.load(app)
|
||||
version = Application.spec(app, :vsn)
|
||||
|
||||
if !version do
|
||||
Mix.raise(
|
||||
"Could not find version for #{inspect(app)}, please make sure the application exists"
|
||||
)
|
||||
end
|
||||
|
||||
to_string(version)
|
||||
|
||||
_ ->
|
||||
version
|
||||
end
|
||||
|
||||
{config_providers, opts} = Keyword.pop(opts, :config_providers, [])
|
||||
{steps, opts} = Keyword.pop(opts, :steps, [:assemble])
|
||||
validate_steps!(steps)
|
||||
@@ -228,36 +247,62 @@ defmodule Mix.Release do
|
||||
end
|
||||
end
|
||||
|
||||
defp load_apps(apps, seen, otp_root) do
|
||||
for app <- apps,
|
||||
not Map.has_key?(seen, app),
|
||||
reduce: seen do
|
||||
seen -> load_app(app, seen, otp_root)
|
||||
defp load_apps(apps, seen, otp_root, included) do
|
||||
for app <- apps, reduce: seen do
|
||||
seen ->
|
||||
if reentrant_seen = reentrant(seen, app, included) do
|
||||
reentrant_seen
|
||||
else
|
||||
load_app(app, seen, otp_root, included)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp load_app(app, seen, otp_root) do
|
||||
defp reentrant(seen, app, included) do
|
||||
properties = seen[app]
|
||||
|
||||
cond do
|
||||
is_nil(properties) ->
|
||||
nil
|
||||
|
||||
included != :maybe and properties[:included] != included ->
|
||||
if properties[:included] == :maybe do
|
||||
put_in(seen[app][:included], included)
|
||||
else
|
||||
Mix.raise(
|
||||
"#{inspect(app)} is listed both as a regular application and as an included application"
|
||||
)
|
||||
end
|
||||
|
||||
true ->
|
||||
seen
|
||||
end
|
||||
end
|
||||
|
||||
defp load_app(app, seen, otp_root, included) do
|
||||
path = Path.join(otp_root, "#{app}-*")
|
||||
|
||||
case Path.wildcard(path) do
|
||||
[] ->
|
||||
case :code.lib_dir(app) do
|
||||
{:error, :bad_name} -> Mix.raise("Could not find application #{inspect(app)}")
|
||||
path -> do_load_app(app, path, seen, otp_root, false)
|
||||
path -> do_load_app(app, path, seen, otp_root, false, included)
|
||||
end
|
||||
|
||||
paths ->
|
||||
path = paths |> Enum.sort() |> List.last()
|
||||
do_load_app(app, to_charlist(path), seen, otp_root, true)
|
||||
do_load_app(app, to_charlist(path), seen, otp_root, true, included)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_load_app(app, path, seen, otp_root, otp_app?) do
|
||||
defp do_load_app(app, path, seen, otp_root, otp_app?, included) do
|
||||
case :file.consult(Path.join(path, "ebin/#{app}.app")) do
|
||||
{:ok, terms} ->
|
||||
[{:application, ^app, properties}] = terms
|
||||
seen = Map.put(seen, app, [path: path, otp_app?: otp_app?] ++ properties)
|
||||
load_apps(Keyword.get(properties, :applications, []), seen, otp_root)
|
||||
value = [path: path, otp_app?: otp_app?, included: included] ++ properties
|
||||
seen = Map.put(seen, app, value)
|
||||
seen = load_apps(Keyword.get(properties, :applications, []), seen, otp_root, false)
|
||||
load_apps(Keyword.get(properties, :included_applications, []), seen, otp_root, true)
|
||||
|
||||
{:error, reason} ->
|
||||
Mix.raise("Could not load #{app}.app. Reason: #{inspect(reason)}")
|
||||
@@ -267,12 +312,16 @@ defmodule Mix.Release do
|
||||
defp build_start_boot(all_apps, specified_apps) do
|
||||
specified_apps ++
|
||||
for(
|
||||
{app, _props} <- all_apps,
|
||||
{app, props} <- all_apps,
|
||||
not List.keymember?(specified_apps, app, 0),
|
||||
do: {app, :permanent}
|
||||
do: {app, default_mode(props)}
|
||||
)
|
||||
end
|
||||
|
||||
defp default_mode(props) do
|
||||
if props[:included] == true, do: :load, else: :permanent
|
||||
end
|
||||
|
||||
defp build_start_clean_boot(boot) do
|
||||
for({app, _mode} <- boot, do: {app, :none})
|
||||
|> Keyword.put(:stdlib, :permanent)
|
||||
@@ -319,9 +368,9 @@ defmodule Mix.Release do
|
||||
path = Path.join(release.version_path, "sys.config")
|
||||
|
||||
args = [runtime?, sys_config]
|
||||
format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~p.~n"
|
||||
format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~tw.~n"
|
||||
File.mkdir_p!(Path.dirname(path))
|
||||
File.write!(path, :io_lib.format(format, args))
|
||||
File.write!(path, :io_lib.format(format, args), [:utf8])
|
||||
|
||||
case :file.consult(path) do
|
||||
{:ok, _} ->
|
||||
@@ -379,7 +428,7 @@ defmodule Mix.Release do
|
||||
end
|
||||
end
|
||||
|
||||
defp random_cookie, do: Base.url_encode64(:crypto.strong_rand_bytes(40))
|
||||
defp random_cookie, do: Base.encode32(:crypto.strong_rand_bytes(32))
|
||||
|
||||
@doc """
|
||||
Makes the start_erl.data file with the
|
||||
@@ -406,7 +455,7 @@ defmodule Mix.Release do
|
||||
:ok | {:error, String.t()}
|
||||
def make_boot_script(release, path, modes, prepend_paths \\ []) do
|
||||
with {:ok, rel_spec} <- build_release_spec(release, modes) do
|
||||
File.write!(path <> ".rel", consultable(rel_spec))
|
||||
File.write!(path <> ".rel", consultable(rel_spec), [:utf8])
|
||||
|
||||
sys_path = String.to_charlist(path)
|
||||
|
||||
@@ -429,7 +478,7 @@ defmodule Mix.Release do
|
||||
|> prepend_paths_to_script(prepend_paths)
|
||||
|
||||
script = {:script, rel_info, instructions}
|
||||
File.write!(script_path, consultable(script))
|
||||
File.write!(script_path, consultable(script), [:utf8])
|
||||
:ok = :systools.script2boot(sys_path)
|
||||
|
||||
{:error, module, info} ->
|
||||
@@ -546,7 +595,7 @@ defmodule Mix.Release do
|
||||
end
|
||||
|
||||
defp consultable(term) do
|
||||
:io_lib.format("%% coding: utf-8~n~p.~n", [term])
|
||||
:io_lib.format("%% coding: utf-8~n~tp.~n", [term])
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -498,7 +498,7 @@ defmodule Mix.Tasks.Format do
|
||||
end
|
||||
|
||||
defp to_bullet_list(files) do
|
||||
Enum.map_join(files, "\n", &" * #{&1}")
|
||||
Enum.map_join(files, "\n", &" * #{&1 |> to_string() |> Path.relative_to_cwd()}")
|
||||
end
|
||||
|
||||
defp equivalent?(input, output) do
|
||||
|
||||
@@ -114,7 +114,15 @@ defmodule Mix.Tasks.Release do
|
||||
distribution. For example, if you need to do some prep work before
|
||||
running the actual system, like migrating your database, `eval` can
|
||||
be a good fit. Just keep in mind any application you may use during
|
||||
eval has to be explicitly started.
|
||||
eval has to be explicitly loaded and/or started.
|
||||
|
||||
You can start an application by calling `Application.ensure_all_started/1`.
|
||||
However, if for some reason you cannot start an application, maybe
|
||||
cause it will run other services you do not want, you must at least
|
||||
load the application by calling `Application.load/1`. If you don't
|
||||
load the application, any attempt at reading its environment or
|
||||
configuration may fail. Note that if you start an application,
|
||||
it is automatically loaded before started.
|
||||
|
||||
Another way to run commands is with `rpc`, which will connect to the
|
||||
system currently running and instruct it to execute the given
|
||||
@@ -133,7 +141,9 @@ defmodule Mix.Tasks.Release do
|
||||
defmodule MyApp.ReleaseTasks do
|
||||
def eval_purge_stale_data() do
|
||||
# Eval commands needs to start the app before
|
||||
# Or Application.load(:my_app) if you can't start it
|
||||
Application.ensure_all_started(:my_app)
|
||||
|
||||
# Code that purges stale data
|
||||
...
|
||||
end
|
||||
@@ -376,8 +386,10 @@ defmodule Mix.Tasks.Release do
|
||||
* `:path` - the path the release should be installed to.
|
||||
Defaults to `"_build/MIX_ENV/rel/RELEASE_NAME"`.
|
||||
|
||||
* `:version` - the release version as a string. Defaults to the current
|
||||
application version.
|
||||
* `:version` - the release version as a string or `{:from_app, app_name}`.
|
||||
Defaults to the current application version. The `{:from_app, app_name}` format
|
||||
can be used to easily reference the application version from another application.
|
||||
This is particularly useful in umbrella applications.
|
||||
|
||||
* `:quiet` - a boolean that controls if releases should write to the standard
|
||||
output its steps. Defaults to `false`.
|
||||
@@ -443,11 +455,25 @@ defmodule Mix.Tasks.Release do
|
||||
to make sure the Erlang Distribution listens only on a given port
|
||||
known at runtime, you can set the following:
|
||||
|
||||
export ELIXIR_ERL_OPTIONS="-kernel inet_dist_listen_min $BEAM_PORT inet_dist_listen_max $BEAM_PORT"
|
||||
case $RELEASE_COMMAND in
|
||||
start*|daemon*)
|
||||
ELIXIR_ERL_OPTIONS="-kernel inet_dist_listen_min $BEAM_PORT inet_dist_listen_max $BEAM_PORT"
|
||||
export ELIXIR_ERL_OPTIONS
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
||||
Or for Windows, in your `env.bat`:
|
||||
Note we only set the port on start/daemon commands. If you also limit
|
||||
the port on other commands, such as `rpc`, then you will be unable
|
||||
to establish a remote connection as the port will already be in use
|
||||
by the node.
|
||||
|
||||
set ELIXIR_ERL_OPTIONS="-kernel inet_dist_listen_min %BEAM_PORT% inet_dist_listen_max %BEAM_PORT%"
|
||||
On Windows, your `env.bat` would look like this:
|
||||
|
||||
IF NOT %RELEASE_COMMAND:start=%==%RELEASE_COMMAND% (
|
||||
set ELIXIR_ERL_OPTIONS="-kernel inet_dist_listen_min %BEAM_PORT% inet_dist_listen_max %BEAM_PORT%"
|
||||
)
|
||||
|
||||
### Steps
|
||||
|
||||
|
||||
@@ -60,11 +60,15 @@ defmodule Mix.Tasks.Release.Init do
|
||||
#!/bin/sh
|
||||
|
||||
# Sets and enables heart (recommended only in daemon mode)
|
||||
# if [ "$RELEASE_COMMAND" = "daemon" ] || [ "$RELEASE_COMMAND" = "daemon_iex" ]; then
|
||||
# HEART_COMMAND="$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND"
|
||||
# export HEART_COMMAND
|
||||
# export ELIXIR_ERL_OPTIONS="-heart"
|
||||
# fi
|
||||
# case $RELEASE_COMMAND in
|
||||
# daemon*)
|
||||
# HEART_COMMAND="$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND"
|
||||
# export HEART_COMMAND
|
||||
# export ELIXIR_ERL_OPTIONS="-heart"
|
||||
# ;;
|
||||
# *)
|
||||
# ;;
|
||||
# esac
|
||||
|
||||
# Set the release to work across nodes. If using the long name format like
|
||||
# the one below (my_app@127.0.0.1), you need to also uncomment the
|
||||
@@ -281,7 +285,7 @@ defmodule Mix.Tasks.Release.Init do
|
||||
findstr "RUNTIME_CONFIG=true" "!RELEASE_SYS_CONFIG!.config" >nul 2>&1 && (
|
||||
for /f "skip=1" %%X in ('wmic os get localdatetime') do if not defined TIMESTAMP set TIMESTAMP=%%X
|
||||
set RELEASE_SYS_CONFIG=!RELEASE_TMP!\!RELEASE_NAME!-!RELEASE_VSN!-!TIMESTAMP:~0,11!-!RANDOM!.runtime
|
||||
mkdir "!RELEASE_TMP!" >nul
|
||||
mkdir "!RELEASE_TMP!" >nul 2>&1
|
||||
copy /y "!REL_VSN_DIR!\sys.config" "!RELEASE_SYS_CONFIG!.config" >nul || (
|
||||
echo Cannot start release because it could not write to "!RELEASE_SYS_CONFIG!.config"
|
||||
goto end
|
||||
@@ -374,7 +378,8 @@ defmodule Mix.Tasks.Release.Init do
|
||||
)
|
||||
|
||||
!ERLSRV! add !RELEASE_NAME!_!RELEASE_NAME! ^
|
||||
-name "!RELEASE_NODE!" ^
|
||||
-!RELEASE_DISTRIBUTION! "!RELEASE_NODE!" ^
|
||||
-env RELEASE_ROOT=!RELEASE_ROOT! -env RELEASE_NAME=!RELEASE_NAME! -env RELEASE_VSN=!RELEASE_VSN! -env RELEASE_COOKIE=!RELEASE_COOKIE! -env RELEASE_NODE=!RELEASE_NODE! -env RELEASE_VM_ARGS=!RELEASE_VM_ARGS! -env RELEASE_TMP=!RELEASE_TMP! -env RELEASE_SYS_CONFIG=!RELEASE_SYS_CONFIG! ^
|
||||
-args "-setcookie !RELEASE_COOKIE! -config !RELEASE_SYS_CONFIG! -mode !RELEASE_MODE! -boot !REL_VSN_DIR!\start -boot_var RELEASE_LIB !RELEASE_ROOT!\lib -args_file !REL_VSN_DIR!\vm.args"
|
||||
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use Mix.Config
|
||||
|
||||
config :release_test, :static, :was_set
|
||||
config :release_test, :encoding, {:"£", "£", '£'}
|
||||
|
||||
+3
-2
@@ -19,11 +19,12 @@ defmodule ReleaseTest do
|
||||
static_config: Application.fetch_env(:release_test, :static),
|
||||
runtime_config: Application.fetch_env(:release_test, :runtime),
|
||||
sys_config_init: to_string(sys_config),
|
||||
sys_config_env: System.get_env("RELEASE_SYS_CONFIG")
|
||||
sys_config_env: System.get_env("RELEASE_SYS_CONFIG"),
|
||||
encoding: Application.get_env(:release_test, :encoding)
|
||||
}
|
||||
|
||||
path = Path.join(System.get_env("RELEASE_ROOT"), "RELEASE_BOOTED")
|
||||
File.write!(path, inspect(info))
|
||||
File.write!(path, :erlang.term_to_binary(info))
|
||||
|
||||
if System.get_env("RELEASE_NAME") =~ "permanent" do
|
||||
Supervisor.start_link([], strategy: :one_for_one)
|
||||
|
||||
@@ -50,6 +50,21 @@ defmodule Mix.ReleaseTest do
|
||||
assert release.options[:quiet]
|
||||
end
|
||||
|
||||
test "allows specifying the version from an application" do
|
||||
overrides = [version: {:from_app, :elixir}]
|
||||
release = from_config!(nil, config(), overrides)
|
||||
|
||||
assert release.version == to_string(Application.spec(:elixir, :vsn))
|
||||
end
|
||||
|
||||
test "raises when :from_app is used with an app that doesn't exist" do
|
||||
overrides = [version: {:from_app, :not_valid}]
|
||||
|
||||
assert_raise Mix.Error,
|
||||
~r"Could not find version for :not_valid, please make sure the application exists",
|
||||
fn -> from_config!(nil, config(), overrides) end
|
||||
end
|
||||
|
||||
test "includes applications" do
|
||||
release = from_config!(nil, config(), [])
|
||||
assert release.applications.mix[:path] == to_charlist(Application.app_dir(:mix))
|
||||
@@ -437,6 +452,14 @@ defmodule Mix.ReleaseTest do
|
||||
assert contents =~ "[{foo,[{bar,baz}]}]."
|
||||
end
|
||||
|
||||
test "writes sys_config with encoding" do
|
||||
assert make_sys_config(release([]), [encoding: {:"£", "£", '£'}], "unused/runtime/path") ==
|
||||
:ok
|
||||
|
||||
{:ok, contents} = :file.consult(@sys_config)
|
||||
assert contents == [[encoding: {:"£", "£", '£'}]]
|
||||
end
|
||||
|
||||
test "writes the given sys_config with config providers" do
|
||||
release = release(config_providers: @providers)
|
||||
assert make_sys_config(release, [kernel: [key: :value]], "/foo/bar/bat") == :ok
|
||||
@@ -569,6 +592,54 @@ defmodule Mix.ReleaseTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "included applications" do
|
||||
test "are included in the release", context do
|
||||
in_tmp(context.test, fn ->
|
||||
app =
|
||||
{:application, :my_sample1,
|
||||
applications: [:kernel, :stdlib, :elixir],
|
||||
description: 'my_sample1',
|
||||
modules: [],
|
||||
vsn: '1.0.0',
|
||||
included_applications: [:runtime_tools]}
|
||||
|
||||
File.mkdir_p!("my_sample1/ebin")
|
||||
Code.prepend_path("my_sample1/ebin")
|
||||
format = :io_lib.format("%% coding: utf-8~n~p.~n", [app])
|
||||
File.write!("my_sample1/ebin/my_sample1.app", format)
|
||||
|
||||
release = release(applications: [my_sample1: :permanent])
|
||||
assert release.applications.runtime_tools[:included]
|
||||
assert release.boot_scripts.start[:runtime_tools] == :load
|
||||
|
||||
release = release(applications: [my_sample1: :permanent, runtime_tools: :none])
|
||||
assert release.applications.runtime_tools[:included]
|
||||
assert release.boot_scripts.start[:runtime_tools] == :none
|
||||
end)
|
||||
end
|
||||
|
||||
test "raise on conflict", context do
|
||||
in_tmp(context.test, fn ->
|
||||
app =
|
||||
{:application, :my_sample2,
|
||||
applications: [:kernel, :stdlib, :elixir, :runtime_tools],
|
||||
description: 'my_sample',
|
||||
modules: [],
|
||||
vsn: '1.0.0',
|
||||
included_applications: [:runtime_tools]}
|
||||
|
||||
File.mkdir_p!("my_sample2/ebin")
|
||||
Code.prepend_path("my_sample2/ebin")
|
||||
format = :io_lib.format("%% coding: utf-8~n~p.~n", [app])
|
||||
File.write!("my_sample2/ebin/my_sample2.app", format)
|
||||
|
||||
assert_raise Mix.Error,
|
||||
":runtime_tools is listed both as a regular application and as an included application",
|
||||
fn -> release(applications: [my_sample2: :permanent]) end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp size!(path) do
|
||||
File.stat!(path).size
|
||||
end
|
||||
|
||||
@@ -140,8 +140,9 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
static_config: {:ok, :was_set},
|
||||
runtime_config: :error,
|
||||
sys_config_env: sys_config_env,
|
||||
sys_config_init: sys_config_init
|
||||
} = wait_until_evaled(Path.join(root, "RELEASE_BOOTED"))
|
||||
sys_config_init: sys_config_init,
|
||||
encoding: {:"£", "£", '£'}
|
||||
} = wait_until_decoded(Path.join(root, "RELEASE_BOOTED"))
|
||||
|
||||
if match?({:win32, _}, :os.type()) do
|
||||
# `RELEAS~1` is the DOS path name (8 character) for the `release_test` directory
|
||||
@@ -169,6 +170,7 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
File.write!("config/releases.exs", """
|
||||
import Config
|
||||
config :release_test, :runtime, :was_set
|
||||
config :release_test, :encoding, {:runtime, :"£", "£", '£'}
|
||||
""")
|
||||
|
||||
root = Path.absname("_build/dev/rel/runtime_config")
|
||||
@@ -198,8 +200,9 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
static_config: {:ok, :was_set},
|
||||
runtime_config: {:ok, :was_set},
|
||||
sys_config_env: sys_config_env,
|
||||
sys_config_init: sys_config_init
|
||||
} = wait_until_evaled(Path.join(root, "RELEASE_BOOTED"))
|
||||
sys_config_init: sys_config_init,
|
||||
encoding: {:runtime, :"£", "£", '£'}
|
||||
} = wait_until_decoded(Path.join(root, "RELEASE_BOOTED"))
|
||||
|
||||
if match?({:win32, _}, :os.type()) do
|
||||
assert sys_config_env =~ "tmp\\runtime_config-0.1.0"
|
||||
@@ -250,7 +253,7 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
root_dir: root_dir,
|
||||
static_config: {:ok, :was_set},
|
||||
runtime_config: :error
|
||||
} = wait_until_evaled(Path.join(root, "RELEASE_BOOTED"))
|
||||
} = wait_until_decoded(Path.join(root, "RELEASE_BOOTED"))
|
||||
|
||||
if match?({:win32, _}, :os.type()) do
|
||||
assert String.ends_with?(app_dir, "demo/lib/release_test-0.1.0")
|
||||
@@ -265,6 +268,7 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
end)
|
||||
end
|
||||
|
||||
@tag :epmd
|
||||
test "executes rpc instructions" do
|
||||
in_fixture("release_test", fn ->
|
||||
config = [releases: [permanent1: [include_erts: false]]]
|
||||
@@ -275,12 +279,13 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
script = Path.join(root, "bin/permanent1")
|
||||
|
||||
open_port(script, ['start'])
|
||||
wait_until_evaled(Path.join(root, "RELEASE_BOOTED"))
|
||||
wait_until_decoded(Path.join(root, "RELEASE_BOOTED"))
|
||||
assert System.cmd(script, ["rpc", "ReleaseTest.hello_world"]) == {"hello world\n", 0}
|
||||
assert System.cmd(script, ["stop"]) == {"", 0}
|
||||
|
||||
assert {pid, 0} = System.cmd(script, ["pid"])
|
||||
assert pid != "\n"
|
||||
|
||||
assert System.cmd(script, ["stop"]) == {"", 0}
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -324,7 +329,7 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
release_vsn: "0.1.0",
|
||||
static_config: {:ok, :was_set},
|
||||
runtime_config: {:ok, :was_set}
|
||||
} = wait_until_evaled(Path.join(root, "RELEASE_BOOTED"))
|
||||
} = wait_until_decoded(Path.join(root, "RELEASE_BOOTED"))
|
||||
end)
|
||||
end)
|
||||
end
|
||||
@@ -341,22 +346,27 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
script = Path.join(root, "bin/permanent2")
|
||||
open_port(script, ['daemon_iex'])
|
||||
|
||||
assert wait_until_evaled(Path.join(root, "RELEASE_BOOTED")) == %{
|
||||
app_dir: Path.join(root, "lib/release_test-0.1.0"),
|
||||
assert %{
|
||||
app_dir: app_dir,
|
||||
cookie_env: "abcdefghij",
|
||||
mode: :embedded,
|
||||
node: :"permanent2@#{@hostname}",
|
||||
node: release_node("permanent2"),
|
||||
protocols_consolidated?: true,
|
||||
release_name: "permanent2",
|
||||
release_node: "permanent2",
|
||||
release_root: root,
|
||||
release_root: ^root,
|
||||
release_vsn: "0.1.0",
|
||||
root_dir: :code.root_dir() |> to_string(),
|
||||
root_dir: root_dir,
|
||||
static_config: {:ok, :was_set},
|
||||
runtime_config: :error,
|
||||
sys_config_env: Path.join(root, "releases/0.1.0/sys"),
|
||||
sys_config_init: Path.join(root, "releases/0.1.0/sys")
|
||||
}
|
||||
sys_config_env: sys_config_env,
|
||||
sys_config_init: sys_config_init
|
||||
} = wait_until_decoded(Path.join(root, "RELEASE_BOOTED"))
|
||||
|
||||
assert app_dir == Path.join(root, "lib/release_test-0.1.0")
|
||||
assert root_dir == :code.root_dir() |> to_string()
|
||||
assert sys_config_env == Path.join(root, "releases/0.1.0/sys")
|
||||
assert sys_config_init == Path.join(root, "releases/0.1.0/sys")
|
||||
|
||||
assert wait_until(fn ->
|
||||
File.read!(Path.join(root, "tmp/log/erlang.log.1")) =~
|
||||
@@ -402,8 +412,13 @@ defmodule Mix.Tasks.ReleaseTest do
|
||||
Port.open({:spawn_executable, to_charlist(command)}, [:hide, args: args])
|
||||
end
|
||||
|
||||
defp wait_until_evaled(file) do
|
||||
wait_until(fn -> File.exists?(file) && Code.eval_file(file) |> elem(0) end)
|
||||
defp wait_until_decoded(file) do
|
||||
wait_until(fn ->
|
||||
case File.read(file) do
|
||||
{:ok, bin} when byte_size(bin) > 0 -> :erlang.binary_to_term(bin)
|
||||
_ -> nil
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp wait_until(fun) do
|
||||
|
||||
@@ -5,8 +5,9 @@ Application.put_env(:mix, :colors, enabled: false)
|
||||
Logger.remove_backend(:console)
|
||||
Application.put_env(:logger, :backends, [])
|
||||
|
||||
exclude = if match?({:win32, _}, :os.type()), do: [unix: true], else: [windows: true]
|
||||
ExUnit.start(trace: "--trace" in System.argv(), exclude: exclude)
|
||||
os_exclude = if match?({:win32, _}, :os.type()), do: [unix: true], else: [windows: true]
|
||||
epmd_exclude = if match?({_, 0}, System.cmd("epmd", ["-daemon"])), do: [], else: [epmd: true]
|
||||
ExUnit.start(trace: "--trace" in System.argv(), exclude: epmd_exclude ++ os_exclude)
|
||||
|
||||
unless {1, 7, 4} <= Mix.SCM.Git.git_version() do
|
||||
IO.puts(:stderr, "Skipping tests with git sparse checkouts...")
|
||||
|
||||
Reference in New Issue
Block a user