Skip to content

Commit 24a188a

Browse files
committed
Share consulting logic between compile.app and release
1 parent 457d52c commit 24a188a

File tree

6 files changed

+129
-128
lines changed

6 files changed

+129
-128
lines changed

lib/elixir/lib/regex.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ defmodule Regex do
258258
This checks the version stored in the regular expression
259259
and recompiles the regex in case of version mismatch.
260260
"""
261-
# Remove me on Elixir v1.22
261+
# TODO: Deprecate on Elixir v1.22
262262
@doc deprecated: "It can be removed and it has no effect"
263263
@doc since: "1.4.0"
264264
def recompile(%Regex{} = regex) do
@@ -268,7 +268,7 @@ defmodule Regex do
268268
@doc """
269269
Recompiles the existing regular expression and raises `Regex.CompileError` in case of errors.
270270
"""
271-
# Remove me on Elixir v1.22
271+
# TODO: Deprecate on Elixir v1.22
272272
@doc deprecated: "It can be removed and it has no effect"
273273
@doc since: "1.4.0"
274274
def recompile!(regex) do
@@ -278,7 +278,7 @@ defmodule Regex do
278278
@doc """
279279
Returns the version of the underlying Regex engine.
280280
"""
281-
# Remove me on Elixir v1.22
281+
# TODO: Deprecate on Elixir v1.22
282282
@doc deprecated: "Use :re.version() instead"
283283
@doc since: "1.4.0"
284284
def version do

lib/mix/lib/mix/release.ex

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -468,51 +468,48 @@ defmodule Mix.Release do
468468
{sys_config, runtime_config?} =
469469
merge_provider_config(release, sys_config, config_provider_path)
470470

471-
path = Path.join(release.version_path, "sys.config")
472-
473-
args = [runtime_config?, sys_config]
474-
format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~tw.~n"
475-
File.mkdir_p!(Path.dirname(path))
476-
File.write!(path, IO.chardata_to_string(:io_lib.format(format, args)))
477-
478-
case :file.consult(path) do
479-
{:ok, _} ->
471+
case consultable_sys_config(sys_config) do
472+
{:ok, contents} ->
473+
path = Path.join(release.version_path, "sys.config")
474+
args = [runtime_config?, contents]
475+
format = "%% coding: utf-8~n%% RUNTIME_CONFIG=~s~n~ts.~n"
476+
File.mkdir_p!(Path.dirname(path))
477+
File.write!(path, IO.chardata_to_string(:io_lib.format(format, args)))
480478
:ok
481479

482-
{:error, reason} ->
483-
invalid =
484-
for {app, kv} <- sys_config,
485-
{key, value} <- kv,
486-
not valid_config?(value),
487-
do: """
488-
489-
Application: #{inspect(app)}
490-
Key: #{inspect(key)}
491-
Value: #{inspect(value)}
492-
"""
493-
494-
message =
495-
case invalid do
496-
[] ->
497-
"Could not read configuration file. Reason: #{inspect(reason)}"
498-
499-
_ ->
500-
"Could not read configuration file. It has invalid configuration terms " <>
501-
"such as functions, references, and pids. Please make sure your configuration " <>
502-
"is made of numbers, atoms, strings, maps, tuples and lists. The following entries " <>
503-
"are wrong:\n#{Enum.join(invalid)}"
504-
end
505-
480+
{:error, message} ->
506481
{:error, message}
507482
end
508483
end
509484

510-
defp valid_config?(m) when is_map(m),
511-
do: Enum.all?(Map.delete(m, :__struct__), &valid_config?/1)
485+
defp consultable_sys_config(sys_config) do
486+
contents =
487+
Enum.map_intersperse(sys_config, ?,, fn {app, kv} ->
488+
kv =
489+
Enum.map_intersperse(kv, ?,, fn {key, value} ->
490+
case Mix.Utils.consultable(value) do
491+
{:ok, value} -> [?{, :io_lib.print(key), ?,, value, ?}]
492+
{:error, term, reason} -> throw({:error, app, key, term, reason})
493+
end
494+
end)
512495

513-
defp valid_config?(l) when is_list(l), do: Enum.all?(l, &valid_config?/1)
514-
defp valid_config?(t) when is_tuple(t), do: Enum.all?(Tuple.to_list(t), &valid_config?/1)
515-
defp valid_config?(o), do: is_number(o) or is_atom(o) or is_binary(o)
496+
[?{, :io_lib.print(app), ?,, ?[, kv, ?], ?}]
497+
end)
498+
499+
{:ok, [?[, contents, ?]]}
500+
catch
501+
{:error, app, key, value, reason} ->
502+
message = """
503+
Could not write configuration file because it has invalid terms
504+
505+
Application: #{inspect(app)}
506+
Key: #{inspect(key)}
507+
Invalid value: #{inspect(value)}
508+
Reason: #{reason}
509+
"""
510+
511+
{:error, message}
512+
end
516513

517514
defp merge_provider_config(%{config_providers: []}, sys_config, _), do: {sys_config, false}
518515

lib/mix/lib/mix/tasks/compile.app.ex

Lines changed: 11 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,17 @@ defmodule Mix.Tasks.Compile.App do
187187
|> add_compile_env(current_properties)
188188
|> add_modules(modules, compile_path)
189189

190-
contents = to_erl_term({:application, app, properties})
190+
contents =
191+
case Mix.Utils.consultable({:application, app, properties}) do
192+
{:ok, contents} ->
193+
contents
194+
195+
{:error, term, reason} ->
196+
Mix.raise(
197+
"\"def application\" has a term which cannot be written to .app files: #{inspect(term)} (#{reason})"
198+
)
199+
end
200+
191201
:application.unload(app)
192202
:application.load({:application, app, properties})
193203

@@ -209,78 +219,6 @@ defmodule Mix.Tasks.Compile.App do
209219
end
210220
end
211221

212-
defp to_erl_term(tuple) when is_tuple(tuple) do
213-
[?{, tuple |> Tuple.to_list() |> to_erl_head(), ?}]
214-
end
215-
216-
defp to_erl_term(list) when is_list(list) do
217-
if List.ascii_printable?(list) do
218-
:io_lib.print(list)
219-
else
220-
[?[, to_erl_head(list), ?]]
221-
end
222-
end
223-
224-
defp to_erl_term(%Regex{re_pattern: {:re_pattern, _, _, _, ref}} = regex)
225-
when is_reference(ref) do
226-
Mix.raise("""
227-
\"def application\" has a term which cannot be written to .app files: #{inspect(regex)}.
228-
Use the E modifier to store regexes in application config.
229-
""")
230-
end
231-
232-
defp to_erl_term(map) when is_map(map) do
233-
inner =
234-
Enum.map_intersperse(
235-
:maps.to_list(:maps.iterator(map, :reversed)),
236-
?,,
237-
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
238-
)
239-
240-
[?#, ?{, inner, ?}]
241-
end
242-
243-
defp to_erl_term(map) when is_map(map) do
244-
inner =
245-
Enum.map_intersperse(
246-
:maps.to_list(:maps.iterator(map, :reversed)),
247-
?,,
248-
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
249-
)
250-
251-
[?#, ?{, inner, ?}]
252-
end
253-
254-
defp to_erl_term(function) when is_function(function) do
255-
fun_info = Function.info(function)
256-
257-
if fun_info[:type] == :external and fun_info[:env] == [] do
258-
:io_lib.print(function)
259-
else
260-
Mix.raise(
261-
"\"def application\" has a function which cannot be written to .app files: #{inspect(function)}" <>
262-
" (only functions in the form &Mod.fun/arity can be part of the application environment)"
263-
)
264-
end
265-
end
266-
267-
defp to_erl_term(term) when is_reference(term) or is_pid(term) do
268-
Mix.raise(
269-
"\"def application\" has a term which cannot be written to .app files: #{inspect(term)}"
270-
)
271-
end
272-
273-
defp to_erl_term(term) do
274-
:io_lib.print(term)
275-
end
276-
277-
defp to_erl_head([]), do: []
278-
defp to_erl_head([h | t]), do: [to_erl_term(h) | to_erl_tail(t)]
279-
280-
defp to_erl_tail([h | t]), do: [?,, to_erl_term(h) | to_erl_tail(t)]
281-
defp to_erl_tail([]), do: []
282-
defp to_erl_tail(other), do: [?|, to_erl_term(other)]
283-
284222
defp current_app_properties(target) do
285223
case :file.consult(target) do
286224
{:ok, [{:application, _app, properties}]} -> properties

lib/mix/lib/mix/utils.ex

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,4 +907,77 @@ defmodule Mix.Utils do
907907
uid
908908
end
909909
end
910+
911+
@doc """
912+
Convert the given terms to a consultable Erlang term.
913+
"""
914+
def consultable(term) do
915+
{:ok, to_erl_term(term)}
916+
catch
917+
{:error, term, reason} -> {:error, term, reason}
918+
end
919+
920+
defp to_erl_term(tuple) when is_tuple(tuple) do
921+
[?{, tuple |> Tuple.to_list() |> to_erl_head(), ?}]
922+
end
923+
924+
defp to_erl_term(list) when is_list(list) do
925+
if List.ascii_printable?(list) do
926+
:io_lib.print(list)
927+
else
928+
[?[, to_erl_head(list), ?]]
929+
end
930+
end
931+
932+
defp to_erl_term(%Regex{re_pattern: {:re_pattern, _, _, _, ref}} = regex)
933+
when is_reference(ref) do
934+
throw({:error, regex, "you must use the /E modifier to store regexes"})
935+
end
936+
937+
defp to_erl_term(map) when is_map(map) do
938+
inner =
939+
Enum.map_intersperse(
940+
:maps.to_list(:maps.iterator(map, :reversed)),
941+
?,,
942+
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
943+
)
944+
945+
[?#, ?{, inner, ?}]
946+
end
947+
948+
defp to_erl_term(map) when is_map(map) do
949+
inner =
950+
Enum.map_intersperse(
951+
:maps.to_list(:maps.iterator(map, :reversed)),
952+
?,,
953+
fn {key, value} -> [to_erl_term(key), "=>", to_erl_term(value)] end
954+
)
955+
956+
[?#, ?{, inner, ?}]
957+
end
958+
959+
defp to_erl_term(function) when is_function(function) do
960+
fun_info = Function.info(function)
961+
962+
if fun_info[:type] == :external and fun_info[:env] == [] do
963+
:io_lib.print(function)
964+
else
965+
throw({:error, function, "only functions in the form &Mod.fun/arity are allowed"})
966+
end
967+
end
968+
969+
defp to_erl_term(term) when is_reference(term) or is_pid(term) do
970+
throw({:error, term, "PIDs and References are not supported"})
971+
end
972+
973+
defp to_erl_term(term) do
974+
:io_lib.print(term)
975+
end
976+
977+
defp to_erl_head([]), do: []
978+
defp to_erl_head([h | t]), do: [to_erl_term(h) | to_erl_tail(t)]
979+
980+
defp to_erl_tail([h | t]), do: [?,, to_erl_term(h) | to_erl_tail(t)]
981+
defp to_erl_tail([]), do: []
982+
defp to_erl_tail(other), do: [?|, to_erl_term(other)]
910983
end

lib/mix/test/mix/release_test.exs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -592,19 +592,20 @@ defmodule Mix.ReleaseTest do
592592
assert make_sys_config(release([]), [foo: [bar: :baz]], "unused/runtime/path") == :ok
593593
contents = File.read!(@sys_config)
594594
assert contents =~ "%% RUNTIME_CONFIG=false"
595-
assert contents =~ "[{foo,[{bar,baz}]}]."
595+
{:ok, contents} = :file.consult(@sys_config)
596+
assert contents == [[foo: [bar: :baz]]]
596597
end
597598

598599
test "writes sys_config with encoding" do
599600
assert make_sys_config(
600601
release([]),
601-
[encoding: {:_μ, :"£", "£", ~c"£"}],
602+
[encoding: [key: {:_μ, :"£", "£", ~c"£"}]],
602603
"unused/runtime/path"
603604
) ==
604605
:ok
605606

606607
{:ok, contents} = :file.consult(@sys_config)
607-
assert contents == [[encoding: {:_μ, :"£", "£", ~c"£"}]]
608+
assert contents == [[encoding: [key: {:_μ, :"£", "£", ~c"£"}]]]
608609
end
609610

610611
test "writes the given sys_config with config providers" do
@@ -649,12 +650,12 @@ defmodule Mix.ReleaseTest do
649650
end
650651

651652
test "errors on bad config" do
652-
assert {:error, "Could not read configuration file." <> _} =
653+
assert {:error, "Could not write configuration file " <> _} =
653654
make_sys_config(release([]), [foo: [bar: self()]], "unused/runtime/path")
654655

655656
env = %{__ENV__ | lexical_tracker: self()}
656657

657-
assert {:error, "Could not read configuration file." <> _} =
658+
assert {:error, "Could not write configuration file " <> _} =
658659
make_sys_config(release([]), [foo: [bar: env]], "unused/runtime/path")
659660
end
660661
end

lib/mix/test/mix/tasks/compile.app_test.exs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -267,25 +267,17 @@ defmodule Mix.Tasks.Compile.AppTest do
267267
test "accepts only regexes without a reference" do
268268
in_fixture("no_mixfile", fn ->
269269
Mix.Project.push(CustomProject)
270-
271270
Process.put(:application, env: [regex: ~r/foo/])
272271

273-
message = """
274-
"def application" has a term which cannot be written to \.app files: ~r\/foo\/.
275-
Use the E modifier to store regexes in application config.
276-
"""
277-
278-
assert_raise Mix.Error, message, fn ->
272+
assert_raise Mix.Error, ~r/you must use the \/E modifier to store regexes/, fn ->
279273
Mix.Tasks.Compile.App.run([])
280274
end
281275

282276
Process.put(:application, env: [exported: ~r/foo/E])
283-
284277
Mix.Tasks.Compile.Elixir.run([])
285278
Mix.Tasks.Compile.App.run([])
286279

287280
properties = parse_resource_file(:custom_project)
288-
289281
assert properties[:env] == [exported: ~r/foo/E]
290282
end)
291283
end

0 commit comments

Comments
 (0)