Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
427d21e410 | ||
|
|
24a188a016 | ||
|
|
457d52c89b | ||
|
|
6684dbd3f2 | ||
|
|
ccb2a742ad | ||
|
|
92094c9ef1 | ||
|
|
d4d5da2ccf | ||
|
|
666c082105 | ||
|
|
d2c9d39efe | ||
|
|
97d16038fe |
@@ -234,6 +234,34 @@ These additions offer greater transparency into the components and licenses of e
|
||||
|
||||
This work was performed by [Jonatan Männchen](https://maennchen.dev) and sponsored by the [Erlang Ecosystem Foundation](https://erlef.org).
|
||||
|
||||
## v1.19.3 (2025-11-13)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Support /E modifier for regular expressions in config files
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Allow forcing specific compilers, such as `--force-elixir`, `--force-app`, etc
|
||||
* [mix help app:APP] Support showing helps for apps in Elixir and Erlang standard libraries
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [IO] Fix dialyzer warning on `IO.inspect :label`
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Case] Fix crash when formatting errors caused by a linked/trapped exit during `setup_all`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.app] Ensure functions in the format `&Mod.fun/arity` can be written to .app files
|
||||
* [mix compile.app] Ensure strings with Unicode characters can be written to .app files
|
||||
|
||||
## v1.19.2 (2025-11-02)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.19.2
|
||||
ELIXIR_VERSION=1.19.3
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
:: SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
:: SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
set ELIXIR_VERSION=1.19.2
|
||||
set ELIXIR_VERSION=1.19.3
|
||||
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
|
||||
|
||||
@@ -569,6 +569,7 @@ defimpl Inspect, for: Regex do
|
||||
defp translate_options([:firstline | t], acc), do: translate_options(t, [?f | acc])
|
||||
defp translate_options([:ungreedy | t], acc), do: translate_options(t, [?U | acc])
|
||||
defp translate_options([:multiline | t], acc), do: translate_options(t, [?m | acc])
|
||||
defp translate_options([:export | t], acc), do: translate_options(t, [?E | acc])
|
||||
defp translate_options([], acc), do: acc
|
||||
defp translate_options(_t, _acc), do: :error
|
||||
|
||||
|
||||
+31
-5
@@ -101,6 +101,13 @@ defmodule Regex do
|
||||
* `:ungreedy` (U) - inverts the "greediness" of the regexp
|
||||
(the previous `r` option is deprecated in favor of `U`)
|
||||
|
||||
* `:export` (E) (since Elixir 1.20) - uses an exported pattern
|
||||
which can be shared across nodes or through config, at the cost of a runtime
|
||||
overhead every time to re-import it every time it is executed.
|
||||
This modifier only has an effect starting on Erlang/OTP 28, and it is ignored
|
||||
on older versions (i.e. `~r/foo/E == ~r/foo/`). This is because patterns cannot
|
||||
and do not need to be exported in order to be shared in these versions.
|
||||
|
||||
## Captures
|
||||
|
||||
Many functions in this module handle what to capture in a regex
|
||||
@@ -251,7 +258,7 @@ defmodule Regex do
|
||||
This checks the version stored in the regular expression
|
||||
and recompiles the regex in case of version mismatch.
|
||||
"""
|
||||
# Remove me on Elixir v1.22
|
||||
# TODO: Deprecate on Elixir v1.22
|
||||
@doc deprecated: "It can be removed and it has no effect"
|
||||
@doc since: "1.4.0"
|
||||
def recompile(%Regex{} = regex) do
|
||||
@@ -261,7 +268,7 @@ defmodule Regex do
|
||||
@doc """
|
||||
Recompiles the existing regular expression and raises `Regex.CompileError` in case of errors.
|
||||
"""
|
||||
# Remove me on Elixir v1.22
|
||||
# TODO: Deprecate on Elixir v1.22
|
||||
@doc deprecated: "It can be removed and it has no effect"
|
||||
@doc since: "1.4.0"
|
||||
def recompile!(regex) do
|
||||
@@ -271,7 +278,7 @@ defmodule Regex do
|
||||
@doc """
|
||||
Returns the version of the underlying Regex engine.
|
||||
"""
|
||||
# Remove me on Elixir v1.22
|
||||
# TODO: Deprecate on Elixir v1.22
|
||||
@doc deprecated: "Use :re.version() instead"
|
||||
@doc since: "1.4.0"
|
||||
def version do
|
||||
@@ -515,7 +522,7 @@ defmodule Regex do
|
||||
"""
|
||||
@spec names(t) :: [String.t()]
|
||||
def names(%Regex{re_pattern: re_pattern}) do
|
||||
{:namelist, names} = :re.inspect(re_pattern, :namelist)
|
||||
{:namelist, names} = :re.inspect(maybe_import_pattern(re_pattern), :namelist)
|
||||
names
|
||||
end
|
||||
|
||||
@@ -585,10 +592,16 @@ defmodule Regex do
|
||||
%Regex{source: source, opts: compile_opts} = regex
|
||||
:re.run(string, source, compile_opts ++ options)
|
||||
else
|
||||
_ -> :re.run(string, re_pattern, options)
|
||||
_ -> :re.run(string, maybe_import_pattern(re_pattern), options)
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, maybe_import_pattern: 1}
|
||||
defp maybe_import_pattern({:re_exported_pattern, _, _, _, _} = exported),
|
||||
do: :re.import(exported)
|
||||
|
||||
defp maybe_import_pattern(pattern), do: pattern
|
||||
|
||||
@typedoc """
|
||||
Options for regex functions that capture matches.
|
||||
"""
|
||||
@@ -1007,6 +1020,16 @@ defmodule Regex do
|
||||
translate_options(t, [:ungreedy | acc])
|
||||
end
|
||||
|
||||
defp translate_options(<<?E, t::binary>>, acc) do
|
||||
# on OTP 27-, the E modifier is a no-op since the feature doesn't exist but isn't needed
|
||||
# (regexes aren't using references and can be shared across nodes or stored in config)
|
||||
# TODO: remove this check on Erlang/OTP 28+ and update docs
|
||||
case Code.ensure_loaded?(:re) and function_exported?(:re, :import, 1) do
|
||||
true -> translate_options(t, [:export | acc])
|
||||
false -> translate_options(t, acc)
|
||||
end
|
||||
end
|
||||
|
||||
defp translate_options(<<>>, acc), do: acc
|
||||
defp translate_options(t, _acc), do: {:error, t}
|
||||
|
||||
@@ -1022,6 +1045,9 @@ defmodule Regex do
|
||||
:erlang.system_info(:otp_release) < [?2, ?8] ->
|
||||
Macro.escape(regex.re_pattern)
|
||||
|
||||
:lists.member(:export, regex.opts) ->
|
||||
Macro.escape(regex.re_pattern)
|
||||
|
||||
# OTP 28.1+ introduced the ability to export and import regexes from compiled binaries
|
||||
Code.ensure_loaded?(:re) and function_exported?(:re, :import, 1) ->
|
||||
{:ok, exported} = :re.compile(regex.source, [:export] ++ regex.opts)
|
||||
|
||||
@@ -906,6 +906,11 @@ defmodule Inspect.OthersTest do
|
||||
assert inspect(Regex.compile!("foo", [:ucp])) == ~S'Regex.compile!("foo", [:ucp])'
|
||||
end
|
||||
|
||||
@tag :re_import
|
||||
test "exported regex" do
|
||||
assert inspect(~r/foo/E) == "~r/foo/E"
|
||||
end
|
||||
|
||||
test "inspect_fun" do
|
||||
fun = fn
|
||||
integer, _opts when is_integer(integer) ->
|
||||
|
||||
@@ -52,6 +52,20 @@ defmodule RegexTest do
|
||||
assert Regex.match?(~r/^b$/m, "a\nb\nc")
|
||||
end
|
||||
|
||||
@tag :re_import
|
||||
test "export" do
|
||||
# exported patterns have no structs, so these are structurally equal
|
||||
assert ~r/foo/E == Regex.compile!("foo", [:export])
|
||||
|
||||
assert Regex.match?(~r/foo/E, "foo")
|
||||
refute Regex.match?(~r/foo/E, "Foo")
|
||||
|
||||
assert Regex.run(~r/c(d)/E, "abcd") == ["cd", "d"]
|
||||
assert Regex.run(~r/e/E, "abcd") == nil
|
||||
|
||||
assert Regex.names(~r/(?<FOO>foo)/E) == ["FOO"]
|
||||
end
|
||||
|
||||
test "precedence" do
|
||||
assert {"aa", :unknown} |> elem(0) =~ ~r/(a)\1/
|
||||
end
|
||||
|
||||
@@ -338,7 +338,8 @@ defmodule ExUnit.Formatter do
|
||||
{formatted_reason, wrapped_stack} =
|
||||
format_exception(test, struct, wrapped_stack, width, formatter, @counter_padding)
|
||||
|
||||
formatted_stack = format_stacktrace(wrapped_stack, test.module, test.name, formatter)
|
||||
formatted_stack = format_stacktrace(wrapped_stack, test, formatter)
|
||||
|
||||
{error_info(header, formatter) <> pad(formatted_reason <> formatted_stack), stack}
|
||||
|
||||
:error ->
|
||||
@@ -672,22 +673,28 @@ defmodule ExUnit.Formatter do
|
||||
|> Algebra.format(width)
|
||||
end
|
||||
|
||||
defp format_stacktrace([], _case, _test, _color) do
|
||||
defp format_stacktrace(stack, %{module: module, name: name}, color),
|
||||
do: format_stacktrace(stack, module, name, color)
|
||||
|
||||
defp format_stacktrace(stack, %{name: name}, color),
|
||||
do: format_stacktrace(stack, name, nil, color)
|
||||
|
||||
defp format_stacktrace([], _module, _test, _color) do
|
||||
""
|
||||
end
|
||||
|
||||
defp format_stacktrace(stacktrace, test_case, test, color) do
|
||||
defp format_stacktrace(stacktrace, module, test, color) do
|
||||
extra_info("stacktrace:", color) <>
|
||||
Enum.map_join(stacktrace, fn entry ->
|
||||
stacktrace_info(format_stacktrace_entry(entry, test_case, test), color)
|
||||
stacktrace_info(format_stacktrace_entry(entry, module, test), color)
|
||||
end)
|
||||
end
|
||||
|
||||
defp format_stacktrace_entry({test_case, test, _, location}, test_case, test) do
|
||||
defp format_stacktrace_entry({module, test, _, location}, module, test) do
|
||||
format_file_line(location[:file], location[:line], " (test)")
|
||||
end
|
||||
|
||||
defp format_stacktrace_entry(entry, _test_case, _test) do
|
||||
defp format_stacktrace_entry(entry, _module, _test) do
|
||||
format_stacktrace_entry(entry)
|
||||
end
|
||||
|
||||
|
||||
@@ -465,6 +465,21 @@ defmodule ExUnit.FormatterTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats setup_all exit stacktraces using module context" do
|
||||
stacktrace = [
|
||||
{Hello, :setup_all, [], [file: __ENV__.file, line: 123]},
|
||||
{Hello, :some_fun, [], [file: __ENV__.file, line: 456]}
|
||||
]
|
||||
|
||||
failure = [{{:EXIT, self()}, {:function_clause, stacktrace}, []}]
|
||||
|
||||
formatted =
|
||||
format_test_all_failure(test_module(), failure, 1, 80, &formatter/2)
|
||||
|
||||
assert formatted =~ "Hello.setup_all/0"
|
||||
assert formatted =~ "Hello.some_fun()"
|
||||
end
|
||||
|
||||
test "formats assertions with operators with no limit" do
|
||||
failure = [{:error, catch_assertion(assert [1, 2, 3] == [4, 5, 6]), []}]
|
||||
|
||||
|
||||
+35
-38
@@ -468,51 +468,48 @@ defmodule Mix.Release do
|
||||
{sys_config, runtime_config?} =
|
||||
merge_provider_config(release, sys_config, config_provider_path)
|
||||
|
||||
path = Path.join(release.version_path, "sys.config")
|
||||
|
||||
args = [runtime_config?, sys_config]
|
||||
format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~tw.~n"
|
||||
File.mkdir_p!(Path.dirname(path))
|
||||
File.write!(path, IO.chardata_to_string(:io_lib.format(format, args)))
|
||||
|
||||
case :file.consult(path) do
|
||||
{:ok, _} ->
|
||||
case consultable_sys_config(sys_config) do
|
||||
{:ok, contents} ->
|
||||
path = Path.join(release.version_path, "sys.config")
|
||||
args = [runtime_config?, contents]
|
||||
format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~ts.~n"
|
||||
File.mkdir_p!(Path.dirname(path))
|
||||
File.write!(path, IO.chardata_to_string(:io_lib.format(format, args)))
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
invalid =
|
||||
for {app, kv} <- sys_config,
|
||||
{key, value} <- kv,
|
||||
not valid_config?(value),
|
||||
do: """
|
||||
|
||||
Application: #{inspect(app)}
|
||||
Key: #{inspect(key)}
|
||||
Value: #{inspect(value)}
|
||||
"""
|
||||
|
||||
message =
|
||||
case invalid do
|
||||
[] ->
|
||||
"Could not read configuration file. Reason: #{inspect(reason)}"
|
||||
|
||||
_ ->
|
||||
"Could not read configuration file. It has invalid configuration terms " <>
|
||||
"such as functions, references, and pids. Please make sure your configuration " <>
|
||||
"is made of numbers, atoms, strings, maps, tuples and lists. The following entries " <>
|
||||
"are wrong:\n#{Enum.join(invalid)}"
|
||||
end
|
||||
|
||||
{:error, message} ->
|
||||
{:error, message}
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_config?(m) when is_map(m),
|
||||
do: Enum.all?(Map.delete(m, :__struct__), &valid_config?/1)
|
||||
defp consultable_sys_config(sys_config) do
|
||||
contents =
|
||||
Enum.map_intersperse(sys_config, ?,, fn {app, kv} ->
|
||||
kv =
|
||||
Enum.map_intersperse(kv, ?,, fn {key, value} ->
|
||||
case Mix.Utils.consultable(value) do
|
||||
{:ok, value} -> [?{, :io_lib.print(key), ?,, value, ?}]
|
||||
{:error, term, reason} -> throw({:error, app, key, term, reason})
|
||||
end
|
||||
end)
|
||||
|
||||
defp valid_config?(l) when is_list(l), do: Enum.all?(l, &valid_config?/1)
|
||||
defp valid_config?(t) when is_tuple(t), do: Enum.all?(Tuple.to_list(t), &valid_config?/1)
|
||||
defp valid_config?(o), do: is_number(o) or is_atom(o) or is_binary(o)
|
||||
[?{, :io_lib.print(app), ?,, ?[, kv, ?], ?}]
|
||||
end)
|
||||
|
||||
{:ok, [?[, contents, ?]]}
|
||||
catch
|
||||
{:error, app, key, value, reason} ->
|
||||
message = """
|
||||
Could not write configuration file because it has invalid terms
|
||||
|
||||
Application: #{inspect(app)}
|
||||
Key: #{inspect(key)}
|
||||
Invalid value: #{inspect(value)}
|
||||
Reason: #{reason}
|
||||
"""
|
||||
|
||||
{:error, message}
|
||||
end
|
||||
|
||||
defp merge_provider_config(%{config_providers: []}, sys_config, _), do: {sys_config, false}
|
||||
|
||||
|
||||
@@ -295,10 +295,24 @@ defmodule Mix.Task.Compiler do
|
||||
end
|
||||
|
||||
defp run_compiler(compiler, args) do
|
||||
result = normalize(Mix.Task.run("compile.#{compiler}", args), compiler)
|
||||
args_maybe_force = maybe_prepend_force(args, compiler)
|
||||
result = normalize(Mix.Task.run("compile.#{compiler}", args_maybe_force), compiler)
|
||||
Enum.reduce(Mix.ProjectStack.pop_after_compiler(compiler), result, & &1.(&2))
|
||||
end
|
||||
|
||||
defp maybe_prepend_force(args, compiler) do
|
||||
args
|
||||
|> Enum.any?(fn
|
||||
"--force-" <> rest -> rest == compiler |> to_string() |> String.replace("_", "-")
|
||||
_ -> false
|
||||
end)
|
||||
|> if do
|
||||
["--force" | args]
|
||||
else
|
||||
args
|
||||
end
|
||||
end
|
||||
|
||||
# Normalize the compiler result to a diagnostic tuple
|
||||
defp normalize(result, name) do
|
||||
case result do
|
||||
|
||||
@@ -187,12 +187,22 @@ defmodule Mix.Tasks.Compile.App do
|
||||
|> add_compile_env(current_properties)
|
||||
|> add_modules(modules, compile_path)
|
||||
|
||||
contents = to_erl_term({:application, app, properties})
|
||||
contents =
|
||||
case Mix.Utils.consultable({:application, app, properties}) do
|
||||
{:ok, contents} ->
|
||||
contents
|
||||
|
||||
{:error, term, reason} ->
|
||||
Mix.raise(
|
||||
"\"def application\" has a term which cannot be written to .app files: #{inspect(term)} (#{reason})"
|
||||
)
|
||||
end
|
||||
|
||||
:application.unload(app)
|
||||
:application.load({:application, app, properties})
|
||||
|
||||
Mix.Project.ensure_structure()
|
||||
File.write!(target, [contents, ?.])
|
||||
File.write!(target, IO.chardata_to_string([contents, ?.]))
|
||||
File.touch!(target, new_mtime)
|
||||
|
||||
# If we just created the .app file, it will have touched
|
||||
@@ -209,53 +219,6 @@ defmodule Mix.Tasks.Compile.App do
|
||||
end
|
||||
end
|
||||
|
||||
defp to_erl_term(tuple) when is_tuple(tuple) do
|
||||
[?{, tuple |> Tuple.to_list() |> to_erl_head(), ?}]
|
||||
end
|
||||
|
||||
defp to_erl_term(list) when is_list(list) do
|
||||
[?[, to_erl_head(list), ?]]
|
||||
end
|
||||
|
||||
defp to_erl_term(map) when is_map(map) do
|
||||
inner =
|
||||
Enum.map_intersperse(
|
||||
:maps.to_list(:maps.iterator(map, :reversed)),
|
||||
?,,
|
||||
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
|
||||
)
|
||||
|
||||
[?#, ?{, inner, ?}]
|
||||
end
|
||||
|
||||
defp to_erl_term(map) when is_map(map) do
|
||||
inner =
|
||||
Enum.map_intersperse(
|
||||
:maps.to_list(:maps.iterator(map, :reversed)),
|
||||
?,,
|
||||
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
|
||||
)
|
||||
|
||||
[?#, ?{, inner, ?}]
|
||||
end
|
||||
|
||||
defp to_erl_term(term) when is_function(term) or is_reference(term) or is_pid(term) do
|
||||
Mix.raise(
|
||||
"\"def application\" has a term which cannot be written to .app files: #{inspect(term)}"
|
||||
)
|
||||
end
|
||||
|
||||
defp to_erl_term(term) do
|
||||
:io_lib.print(term)
|
||||
end
|
||||
|
||||
defp to_erl_head([]), do: []
|
||||
defp to_erl_head([h | t]), do: [to_erl_term(h) | to_erl_tail(t)]
|
||||
|
||||
defp to_erl_tail([h | t]), do: [?,, to_erl_term(h) | to_erl_tail(t)]
|
||||
defp to_erl_tail([]), do: []
|
||||
defp to_erl_tail(other), do: [?|, to_erl_term(other)]
|
||||
|
||||
defp current_app_properties(target) do
|
||||
case :file.consult(target) do
|
||||
{:ok, [{:application, _app, properties}]} -> properties
|
||||
|
||||
@@ -7,7 +7,7 @@ defmodule Mix.Tasks.Compile do
|
||||
|
||||
@shortdoc "Compiles source files"
|
||||
|
||||
@moduledoc """
|
||||
@moduledoc ~S"""
|
||||
The main entry point to compile source files.
|
||||
|
||||
It simply runs the compilers registered in your project and returns
|
||||
@@ -59,7 +59,9 @@ defmodule Mix.Tasks.Compile do
|
||||
* `--all-warnings` (`--no-all-warnings`) - prints all warnings, including previous compilations
|
||||
(default is true except on errors)
|
||||
* `--erl-config` - path to an Erlang term file that will be loaded as Mix config
|
||||
* `--force` - forces compilation
|
||||
* `--force` - forces compilation. You can also specify `--force-#{compiler}` for each compiler
|
||||
invoked to force specifically that compiler to run (it requires the compiler to respect the
|
||||
`--force` option, which is advised)
|
||||
* `--list` - lists all enabled compilers
|
||||
* `--listeners` - starts Mix listeners (they are started by default,
|
||||
unless `--no-listeners` or `--no-deps-check` are given)
|
||||
|
||||
@@ -114,10 +114,19 @@ defmodule Mix.Tasks.Help do
|
||||
loadpaths!()
|
||||
app = String.to_atom(app)
|
||||
|
||||
# If the application is not available, attempt to load it from Erlang/Elixir
|
||||
if is_nil(Application.spec(app, :vsn)) do
|
||||
try do
|
||||
Mix.ensure_application!(app)
|
||||
rescue
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
|
||||
if modules = Application.spec(app, :modules) do
|
||||
for module <- modules,
|
||||
not (module |> Atom.to_string() |> String.starts_with?("Elixir.Mix.Tasks.")),
|
||||
{:docs_v1, _, :elixir, "text/markdown", %{"en" => <<doc::binary>>}, _, _} <-
|
||||
{:docs_v1, _, _, "text/markdown", %{"en" => <<doc::binary>>}, _, _} <-
|
||||
[Code.fetch_docs(module)] do
|
||||
leading = doc |> String.split(["\n\n", "\r\n\r\n"], parts: 2) |> hd()
|
||||
"# #{inspect(module)}\n#{leading}\n"
|
||||
|
||||
@@ -907,4 +907,77 @@ defmodule Mix.Utils do
|
||||
uid
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert the given terms to a consultable Erlang term.
|
||||
"""
|
||||
def consultable(term) do
|
||||
{:ok, to_erl_term(term)}
|
||||
catch
|
||||
{:error, term, reason} -> {:error, term, reason}
|
||||
end
|
||||
|
||||
defp to_erl_term(tuple) when is_tuple(tuple) do
|
||||
[?{, tuple |> Tuple.to_list() |> to_erl_head(), ?}]
|
||||
end
|
||||
|
||||
defp to_erl_term(list) when is_list(list) do
|
||||
if List.ascii_printable?(list) do
|
||||
:io_lib.print(list)
|
||||
else
|
||||
[?[, to_erl_head(list), ?]]
|
||||
end
|
||||
end
|
||||
|
||||
defp to_erl_term(%Regex{re_pattern: {:re_pattern, _, _, _, ref}} = regex)
|
||||
when is_reference(ref) do
|
||||
throw({:error, regex, "you must use the /E modifier to store regexes"})
|
||||
end
|
||||
|
||||
defp to_erl_term(map) when is_map(map) do
|
||||
inner =
|
||||
Enum.map_intersperse(
|
||||
:maps.to_list(:maps.iterator(map, :reversed)),
|
||||
?,,
|
||||
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
|
||||
)
|
||||
|
||||
[?#, ?{, inner, ?}]
|
||||
end
|
||||
|
||||
defp to_erl_term(map) when is_map(map) do
|
||||
inner =
|
||||
Enum.map_intersperse(
|
||||
:maps.to_list(:maps.iterator(map, :reversed)),
|
||||
?,,
|
||||
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
|
||||
)
|
||||
|
||||
[?#, ?{, inner, ?}]
|
||||
end
|
||||
|
||||
defp to_erl_term(function) when is_function(function) do
|
||||
fun_info = Function.info(function)
|
||||
|
||||
if fun_info[:type] == :external and fun_info[:env] == [] do
|
||||
:io_lib.print(function)
|
||||
else
|
||||
throw({:error, function, "only functions in the form &Mod.fun/arity are allowed"})
|
||||
end
|
||||
end
|
||||
|
||||
defp to_erl_term(term) when is_reference(term) or is_pid(term) do
|
||||
throw({:error, term, "PIDs and References are not supported"})
|
||||
end
|
||||
|
||||
defp to_erl_term(term) do
|
||||
:io_lib.print(term)
|
||||
end
|
||||
|
||||
defp to_erl_head([]), do: []
|
||||
defp to_erl_head([h | t]), do: [to_erl_term(h) | to_erl_tail(t)]
|
||||
|
||||
defp to_erl_tail([h | t]), do: [?,, to_erl_term(h) | to_erl_tail(t)]
|
||||
defp to_erl_tail([]), do: []
|
||||
defp to_erl_tail(other), do: [?|, to_erl_term(other)]
|
||||
end
|
||||
|
||||
@@ -592,19 +592,20 @@ defmodule Mix.ReleaseTest do
|
||||
assert make_sys_config(release([]), [foo: [bar: :baz]], "unused/runtime/path") == :ok
|
||||
contents = File.read!(@sys_config)
|
||||
assert contents =~ "%% RUNTIME_CONFIG=false"
|
||||
assert contents =~ "[{foo,[{bar,baz}]}]."
|
||||
{:ok, contents} = :file.consult(@sys_config)
|
||||
assert contents == [[foo: [bar: :baz]]]
|
||||
end
|
||||
|
||||
test "writes sys_config with encoding" do
|
||||
assert make_sys_config(
|
||||
release([]),
|
||||
[encoding: {:_μ, :"£", "£", ~c"£"}],
|
||||
[encoding: [key: {:_μ, :"£", "£", ~c"£"}]],
|
||||
"unused/runtime/path"
|
||||
) ==
|
||||
:ok
|
||||
|
||||
{:ok, contents} = :file.consult(@sys_config)
|
||||
assert contents == [[encoding: {:_μ, :"£", "£", ~c"£"}]]
|
||||
assert contents == [[encoding: [key: {:_μ, :"£", "£", ~c"£"}]]]
|
||||
end
|
||||
|
||||
test "writes the given sys_config with config providers" do
|
||||
@@ -649,12 +650,12 @@ defmodule Mix.ReleaseTest do
|
||||
end
|
||||
|
||||
test "errors on bad config" do
|
||||
assert {:error, "Could not read configuration file." <> _} =
|
||||
assert {:error, "Could not write configuration file " <> _} =
|
||||
make_sys_config(release([]), [foo: [bar: self()]], "unused/runtime/path")
|
||||
|
||||
env = %{__ENV__ | lexical_tracker: self()}
|
||||
|
||||
assert {:error, "Could not read configuration file." <> _} =
|
||||
assert {:error, "Could not write configuration file " <> _} =
|
||||
make_sys_config(release([]), [foo: [bar: env]], "unused/runtime/path")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -110,7 +110,7 @@ defmodule Mix.Tasks.Compile.AppTest do
|
||||
test "uses custom application settings" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
Mix.Project.push(CustomProject)
|
||||
env = [foo: [:one, "two", 3, 4], bar: [{} | %{foo: :bar}]]
|
||||
env = [foo: [:one, "two", 3, 4, "฿", &List.flatten/1], bar: [{} | %{foo: :bar}]]
|
||||
|
||||
Process.put(:application,
|
||||
maxT: :infinity,
|
||||
@@ -132,7 +132,10 @@ defmodule Mix.Tasks.Compile.AppTest do
|
||||
assert properties[:applications] ==
|
||||
[:kernel, :stdlib, :elixir, :logger, :ex_unit, :example_app, :mix]
|
||||
|
||||
assert properties[:env] == [foo: [:one, "two", 3, 4], bar: [{} | %{foo: :bar}]]
|
||||
assert properties[:env] == [
|
||||
foo: [:one, "two", 3, 4, "฿", &List.flatten/1],
|
||||
bar: [{} | %{foo: :bar}]
|
||||
]
|
||||
|
||||
refute Keyword.has_key?(properties, :extra_applications)
|
||||
end)
|
||||
@@ -260,6 +263,25 @@ defmodule Mix.Tasks.Compile.AppTest do
|
||||
end)
|
||||
end
|
||||
|
||||
@tag :re_import
|
||||
test "accepts only regexes without a reference" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
Mix.Project.push(CustomProject)
|
||||
Process.put(:application, env: [regex: ~r/foo/])
|
||||
|
||||
assert_raise Mix.Error, ~r/you must use the \/E modifier to store regexes/, fn ->
|
||||
Mix.Tasks.Compile.App.run([])
|
||||
end
|
||||
|
||||
Process.put(:application, env: [exported: ~r/foo/E])
|
||||
Mix.Tasks.Compile.Elixir.run([])
|
||||
Mix.Tasks.Compile.App.run([])
|
||||
|
||||
properties = parse_resource_file(:custom_project)
|
||||
assert properties[:env] == [exported: ~r/foo/E]
|
||||
end)
|
||||
end
|
||||
|
||||
test ".app contains description and registered (as required by systools)" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
Mix.Project.push(MixTest.Case.Sample)
|
||||
|
||||
@@ -1253,6 +1253,23 @@ defmodule Mix.Tasks.Compile.ElixirTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "recompiles with --force-elixir" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
Mix.Project.push(MixTest.Case.Sample)
|
||||
assert Mix.Task.run("compile") == {:ok, []}
|
||||
purge([A, B])
|
||||
|
||||
# Now we have a noop
|
||||
Mix.Task.clear()
|
||||
assert Mix.Task.run("compile") == {:noop, []}
|
||||
|
||||
# --force-elixir
|
||||
Mix.Task.clear()
|
||||
assert Mix.Task.run("compile", ["--verbose", "--force-elixir"]) == {:ok, []}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
end)
|
||||
end
|
||||
|
||||
test "compiles files with autoload disabled" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
Mix.Project.push(MixTest.Case.Sample)
|
||||
|
||||
@@ -199,14 +199,23 @@ defmodule Mix.Tasks.HelpTest do
|
||||
in_tmp(context.test, fn ->
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Mix.Tasks.Help.run(["app:mix"])
|
||||
Mix.Tasks.Help.run(["app:iex"])
|
||||
end)
|
||||
|
||||
assert output =~ "# Mix\n\nMix is a build tool"
|
||||
assert output =~ "# IEx\n\nElixir's interactive shell."
|
||||
|
||||
if System.otp_release() >= "27" do
|
||||
output =
|
||||
capture_io(fn ->
|
||||
Mix.Tasks.Help.run(["app:parsetools"])
|
||||
end)
|
||||
|
||||
assert output =~ "# :leex"
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test "help unknown app:APP", context do
|
||||
test "help app:UNKNOWN", context do
|
||||
in_tmp(context.test, fn ->
|
||||
Mix.Tasks.Help.run(["app:foobar"])
|
||||
assert_received {:mix_shell, :error, ["Application foobar does not exist or is not loaded"]}
|
||||
|
||||
@@ -43,12 +43,22 @@ cover_exclude =
|
||||
[]
|
||||
end
|
||||
|
||||
# OTP 28.1+
|
||||
re_import_exclude =
|
||||
if Code.ensure_loaded?(:re) and function_exported?(:re, :import, 1) do
|
||||
[]
|
||||
else
|
||||
[:re_import]
|
||||
end
|
||||
|
||||
Code.require_file("../../elixir/scripts/cover_record.exs", __DIR__)
|
||||
CoverageRecorder.maybe_record("mix")
|
||||
|
||||
ExUnit.start(
|
||||
trace: !!System.get_env("TRACE"),
|
||||
exclude: epmd_exclude ++ os_exclude ++ git_exclude ++ line_exclude ++ cover_exclude,
|
||||
exclude:
|
||||
epmd_exclude ++
|
||||
os_exclude ++ git_exclude ++ line_exclude ++ cover_exclude ++ re_import_exclude,
|
||||
include: line_include,
|
||||
assert_receive_timeout: String.to_integer(System.get_env("ELIXIR_ASSERT_TIMEOUT", "300"))
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user