diff --git a/lib/mix/lib/mix/tasks/compile.app.ex b/lib/mix/lib/mix/tasks/compile.app.ex index 7383b4a8802..d44968cd1a0 100644 --- a/lib/mix/lib/mix/tasks/compile.app.ex +++ b/lib/mix/lib/mix/tasks/compile.app.ex @@ -187,11 +187,11 @@ defmodule Mix.Tasks.Compile.App do |> add_compile_env(current_properties) |> add_modules(modules, compile_path) - contents = :io_lib.format("~p.~n", [{:application, app, properties}]) + contents = to_erl_term({:application, app, properties}) :application.load({:application, app, properties}) Mix.Project.ensure_structure() - File.write!(target, IO.chardata_to_string(contents)) + File.write!(target, [contents, ?.]) File.touch!(target, new_mtime) # If we just created the .app file, it will have touched @@ -208,6 +208,53 @@ 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 diff --git a/lib/mix/test/mix/tasks/compile.app_test.exs b/lib/mix/test/mix/tasks/compile.app_test.exs index 9a8926412f5..b89a6866dc7 100644 --- a/lib/mix/test/mix/tasks/compile.app_test.exs +++ b/lib/mix/test/mix/tasks/compile.app_test.exs @@ -7,24 +7,6 @@ Code.require_file("../../test_helper.exs", __DIR__) defmodule Mix.Tasks.Compile.AppTest do use MixTest.Case - defmodule CustomProject do - def project do - [ - app: :custom_project, - version: "0.2.0", - description: "Some UTF-8 dëscriptión" - ] - end - - def application do - [ - maxT: :infinity, - applications: [:example_app, mix: :optional], - extra_applications: [:logger, ex_unit: :optional] - ] - end - end - defmodule CustomDeps do def project do [app: :custom_deps, version: "0.2.0", deps: deps()] @@ -51,9 +33,13 @@ defmodule Mix.Tasks.Compile.AppTest do end end - defmodule InvalidProject do + defmodule CustomProject do def project do - [app: :invalid_project, version: "0.3.0"] + [ + app: :custom_project, + version: "0.3.0", + description: "Some UTF-8 dëscriptión" + ] end def application do @@ -124,13 +110,21 @@ 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}]] + + Process.put(:application, + maxT: :infinity, + applications: [:example_app, mix: :optional], + extra_applications: [:logger, ex_unit: :optional], + env: env + ) Mix.Tasks.Compile.Elixir.run([]) Mix.Tasks.Compile.App.run([]) properties = parse_resource_file(:custom_project) - assert Application.spec(:custom_project, :vsn) == ~c"0.2.0" - assert properties[:vsn] == ~c"0.2.0" + assert Application.spec(:custom_project, :vsn) == ~c"0.3.0" + assert properties[:vsn] == ~c"0.3.0" assert properties[:maxT] == :infinity assert properties[:optional_applications] == [:ex_unit, :mix] assert properties[:description] == ~c"Some UTF-8 dëscriptión" @@ -138,6 +132,8 @@ 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}]] + refute Keyword.has_key?(properties, :extra_applications) end) end @@ -160,7 +156,7 @@ defmodule Mix.Tasks.Compile.AppTest do test "validates properties" do in_fixture("no_mixfile", fn -> - Mix.Project.push(InvalidProject) + Mix.Project.push(CustomProject) Process.put(:application, [:not_a_keyword, applications: []]) message = "Application configuration returned from application/0 should be a keyword list" @@ -252,6 +248,15 @@ defmodule Mix.Tasks.Compile.AppTest do assert_raise Mix.Error, message, fn -> Mix.Tasks.Compile.App.run([]) end + + Process.put(:application, env: [foo: make_ref()]) + + message = + ~r"\"def application\" has a term which cannot be written to \.app files: #Reference" + + assert_raise Mix.Error, message, fn -> + Mix.Tasks.Compile.App.run([]) + end end) end