Skip to content

Commit 75e2ee0

Browse files
authored
Automatically inflect the list of applications (#5473)
Closes #5249
1 parent 21f219d commit 75e2ee0

File tree

6 files changed

+108
-50
lines changed

6 files changed

+108
-50
lines changed

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

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ defmodule Mix.Tasks.Compile.App do
1818
function in your `mix.exs` with the following options:
1919
2020
* `:applications` - all applications your application depends
21-
on at runtime. For example, if your application depends on
22-
Erlang's `:crypto`, it needs to be added to this list. Most
23-
of your dependencies must be added as well (unless they're
24-
a development or test dependency). Mix and other tools use this
25-
list in order to properly boot your application dependencies
21+
on at runtime. By default, this list is automatically inflected
22+
from your dependencies. Any extra Erlang/Elixir dependency
23+
must be specified in `:extra_applications`. Mix and other tools
24+
use the application list in order to start your dependencies
2625
before starting the application itself.
2726
27+
* `:extra_applications` - a list of Erlang/Elixir applications
28+
that you want started before your application. For example,
29+
Elixir's `:logger` or Erlang's `:crypto`.
30+
2831
* `:registered` - the name of all registered processes in the
2932
application. If your application defines a local GenServer
3033
with name `MyServer`, it is recommended to add `MyServer`
@@ -79,10 +82,10 @@ defmodule Mix.Tasks.Compile.App do
7982

8083
if opts[:force] || Mix.Utils.stale?(sources, [target]) || modules_changed?(mods, target) do
8184
best_guess = [
82-
vsn: to_charlist(version),
85+
description: to_charlist(config[:description] || app),
8386
modules: mods,
84-
applications: [],
8587
registered: [],
88+
vsn: to_charlist(version),
8689
]
8790

8891
properties = if function_exported?(project, :application, 0) do
@@ -95,13 +98,7 @@ defmodule Mix.Tasks.Compile.App do
9598
best_guess
9699
end
97100

98-
properties = ensure_correct_properties(app, config, properties)
99-
100-
# Ensure we always prepend the standard application dependencies
101-
properties = Keyword.update!(properties, :applications, fn apps ->
102-
[:kernel, :stdlib] ++ language_app(config) ++ apps
103-
end)
104-
101+
properties = ensure_correct_properties(properties, config)
105102
contents = :io_lib.format("~p.~n", [{:application, app, properties}])
106103

107104
Mix.Project.ensure_structure()
@@ -152,13 +149,14 @@ defmodule Mix.Tasks.Compile.App do
152149
end
153150
end
154151

155-
defp ensure_correct_properties(app, config, properties) do
152+
defp ensure_correct_properties(properties, config) do
156153
properties
157-
|> Keyword.put_new(:description, to_charlist(config[:description] || app))
158-
|> validate_properties
154+
|> validate_properties!
155+
|> Keyword.put_new_lazy(:applications, fn -> apps_from_prod_non_optional_deps(properties) end)
156+
|> Keyword.update!(:applications, fn apps -> normalize_apps(apps, properties, config) end)
159157
end
160158

161-
defp validate_properties(properties) do
159+
defp validate_properties!(properties) do
162160
Enum.each properties, fn
163161
{:description, value} ->
164162
unless is_list(value) do
@@ -188,13 +186,17 @@ defmodule Mix.Tasks.Compile.App do
188186
unless is_list(value) and Enum.all?(value, &is_atom(&1)) do
189187
Mix.raise "Application included applications (:included_applications) should be a list of atoms, got: #{inspect value}"
190188
end
189+
{:extra_applications, value} ->
190+
unless is_list(value) and Enum.all?(value, &is_atom(&1)) do
191+
Mix.raise "Application extra applications (:extra_applications) should be a list of atoms, got: #{inspect value}"
192+
end
191193
{:applications, value} ->
192194
unless is_list(value) and Enum.all?(value, &is_atom(&1)) do
193-
Mix.raise "Application dependencies (:applications) should be a list of atoms, got: #{inspect value}"
195+
Mix.raise "Application applications (:applications) should be a list of atoms, got: #{inspect value}"
194196
end
195197
{:env, value} ->
196198
unless Keyword.keyword?(value) do
197-
Mix.raise "Application dependencies (:env) should be a keyword list, got: #{inspect value}"
199+
Mix.raise "Application environment (:env) should be a keyword list, got: #{inspect value}"
198200
end
199201
{:start_phases, value} ->
200202
unless Keyword.keyword?(value) do
@@ -212,4 +214,20 @@ defmodule Mix.Tasks.Compile.App do
212214

213215
properties
214216
end
217+
218+
defp apps_from_prod_non_optional_deps(properties) do
219+
included_applications = Keyword.get(properties, :included_applications, [])
220+
221+
for %{app: app, opts: opts, top_level: true} <- Mix.Dep.cached,
222+
Keyword.get(opts, :app, true),
223+
Keyword.get(opts, :runtime, true),
224+
not Keyword.get(opts, :optional, false),
225+
not app in included_applications,
226+
do: app
227+
end
228+
229+
defp normalize_apps(apps, properties, config) do
230+
extra = Keyword.get(properties, :extra_applications, [])
231+
Enum.uniq([:kernel, :stdlib] ++ language_app(config) ++ extra ++ apps)
232+
end
215233
end

lib/mix/lib/mix/tasks/deps.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ defmodule Mix.Tasks.Deps do
8181
try to infer the type of project but it can be overridden with this
8282
option by setting it to `:mix`, `:rebar`, `:rebar3` or `:make`
8383
84+
* `:runtime` - whether the dependency is part of runtime applications.
85+
Defaults to `true` which automatically adds the application to the list
86+
of apps that are started automatically and included in releases
87+
8488
### Git options (`:git`)
8589
8690
* `:git` - the Git repository URI

lib/mix/lib/mix/tasks/new.ex

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,11 @@ defmodule Mix.Tasks.New do
113113
end
114114

115115
defp otp_app(_mod, false) do
116-
" [applications: [:logger]]"
116+
" [extra_applications: [:logger]]"
117117
end
118118

119119
defp otp_app(mod, true) do
120-
" [applications: [:logger],\n mod: {#{mod}.Application, []}]"
120+
" [extra_applications: [:logger],\n mod: {#{mod}.Application, []}]"
121121
end
122122

123123
defp cd_path(".") do
@@ -218,23 +218,14 @@ defmodule Mix.Tasks.New do
218218
<%= if @app do %>
219219
## Installation
220220
221-
If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:
221+
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
222+
by adding `<%= @app %>` to your list of dependencies in `mix.exs`:
222223
223-
1. Add `<%= @app %>` to your list of dependencies in `mix.exs`:
224-
225-
```elixir
226-
def deps do
227-
[{:<%= @app %>, "~> 0.1.0"}]
228-
end
229-
```
230-
231-
2. Ensure `<%= @app %>` is started before your application:
232-
233-
```elixir
234-
def application do
235-
[applications: [:<%= @app %>]]
236-
end
237-
```
224+
```elixir
225+
def deps do
226+
[{:<%= @app %>, "~> 0.1.0"}]
227+
end
228+
```
238229
239230
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
240231
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
@@ -255,14 +246,14 @@ defmodule Mix.Tasks.New do
255246
# Where 3rd-party dependencies like ExDoc output generated docs.
256247
/doc
257248
249+
# Ignore .fetch files in case you like to edit your project deps locally.
250+
/.fetch
251+
258252
# If the VM crashes, it generates a dump, let's ignore it too.
259253
erl_crash.dump
260254
261255
# Also ignore archive artifacts (built via "mix archive.build").
262256
*.ez
263-
264-
# Ignore .fetch files in case you like to edit your project deps locally.
265-
/.fetch
266257
"""
267258

268259
embed_template :mixfile, """
@@ -282,6 +273,7 @@ defmodule Mix.Tasks.New do
282273
#
283274
# Type "mix help compile.app" for more information
284275
def application do
276+
# Specify extra applications you'll use from Erlang/Elixir
285277
<%= @otp_app %>
286278
end
287279
@@ -321,6 +313,7 @@ defmodule Mix.Tasks.New do
321313
#
322314
# Type "mix help compile.app" for more information
323315
def application do
316+
# Specify extra applications you'll use from Erlang/Elixir
324317
<%= @otp_app %>
325318
end
326319

lib/mix/test/mix/tasks/app.tree_test.exs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,9 @@ defmodule Mix.Tasks.App.TreeTest do
3434
in_fixture "umbrella_dep/deps/umbrella", fn ->
3535
Mix.Project.in_project(:umbrella, ".", fn _ ->
3636
Mix.Task.run "app.tree", ["--format", "pretty"]
37+
assert_received {:mix_shell, :info, ["├── elixir"]}
3738
assert_received {:mix_shell, :info, ["foo"]}
38-
assert_received {:mix_shell, :info, ["└── elixir"]}
39-
assert_received {:mix_shell, :info, ["bar"]}
40-
assert_received {:mix_shell, :info, ["└── elixir"]}
39+
assert_received {:mix_shell, :info, [" └── elixir"]}
4140
end)
4241
end
4342
end

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

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,30 @@ defmodule Mix.Tasks.Compile.AppTest do
1111

1212
def application do
1313
[maxT: :infinity,
14-
applications: [:example_app]]
14+
applications: [:example_app],
15+
extra_applications: [:logger]]
16+
end
17+
end
18+
19+
defmodule CustomDeps do
20+
def project do
21+
[app: :custom_deps, version: "0.2.0", deps: deps()]
22+
end
23+
24+
def application do
25+
[extra_applications: [:logger], included_applications: [:ok9]]
26+
end
27+
28+
def deps do
29+
[{:ok1, path: "../ok"},
30+
{:ok2, path: "../ok", only: :prod},
31+
{:ok3, path: "../ok", only: :dev},
32+
{:ok4, path: "../ok", runtime: true},
33+
{:ok5, path: "../ok", runtime: false},
34+
{:ok6, path: "../ok", optional: true},
35+
{:ok7, path: "../ok", optional: false},
36+
{:ok8, path: "../ok", app: false},
37+
{:ok9, path: "../ok"}]
1538
end
1639
end
1740

@@ -48,7 +71,7 @@ defmodule Mix.Tasks.Compile.AppTest do
4871
end
4972
end
5073

51-
test "use custom application settings" do
74+
test "uses custom application settings" do
5275
Mix.Project.push CustomProject
5376

5477
in_fixture "no_mixfile", fn ->
@@ -57,11 +80,22 @@ defmodule Mix.Tasks.Compile.AppTest do
5780
contents = File.read!("_build/dev/lib/custom_project/ebin/custom_project.app")
5881
assert contents =~ "0.2.0"
5982
assert contents =~ "{maxT,infinity}"
60-
assert contents =~ "{applications,[kernel,stdlib,elixir,example_app]}"
83+
assert contents =~ "{applications,[kernel,stdlib,elixir,logger,example_app]}"
6184
assert contents =~ "Some UTF-8 description (uma descrição em UTF-8)"
6285
end
6386
end
6487

88+
test "automatically inflects applications" do
89+
Mix.Project.push CustomDeps
90+
91+
in_fixture "no_mixfile", fn ->
92+
Mix.Tasks.Compile.Elixir.run([])
93+
Mix.Tasks.Compile.App.run([])
94+
contents = File.read!("_build/dev/lib/custom_deps/ebin/custom_deps.app")
95+
assert contents =~ "{applications,[kernel,stdlib,elixir,logger,ok1,ok3,ok4,ok7]}"
96+
end
97+
end
98+
6599
test "application properties validation" do
66100
Mix.Project.push InvalidProject
67101

@@ -90,26 +124,32 @@ defmodule Mix.Tasks.Compile.AppTest do
90124
Mix.Tasks.Compile.App.run([])
91125
end
92126

127+
Process.put(:application, [extra_applications: ["invalid"]])
128+
message = "Application extra applications (:extra_applications) should be a list of atoms, got: [\"invalid\"]"
129+
assert_raise Mix.Error, message, fn ->
130+
Mix.Tasks.Compile.App.run([])
131+
end
132+
93133
Process.put(:application, [included_applications: ["invalid"]])
94134
message = "Application included applications (:included_applications) should be a list of atoms, got: [\"invalid\"]"
95135
assert_raise Mix.Error, message, fn ->
96136
Mix.Tasks.Compile.App.run([])
97137
end
98138

99139
Process.put(:application, [applications: ["invalid"]])
100-
message = "Application dependencies (:applications) should be a list of atoms, got: [\"invalid\"]"
140+
message = "Application applications (:applications) should be a list of atoms, got: [\"invalid\"]"
101141
assert_raise Mix.Error, message, fn ->
102142
Mix.Tasks.Compile.App.run([])
103143
end
104144

105145
Process.put(:application, [applications: nil])
106-
message = "Application dependencies (:applications) should be a list of atoms, got: nil"
146+
message = "Application applications (:applications) should be a list of atoms, got: nil"
107147
assert_raise Mix.Error, message, fn ->
108148
Mix.Tasks.Compile.App.run([])
109149
end
110150

111151
Process.put(:application, [env: [:invalid]])
112-
message = "Application dependencies (:env) should be a keyword list, got: [:invalid]"
152+
message = "Application environment (:env) should be a keyword list, got: [:invalid]"
113153
assert_raise Mix.Error, message, fn ->
114154
Mix.Tasks.Compile.App.run([])
115155
end

lib/mix/test/mix/tasks/escript_test.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ defmodule Mix.Tasks.EscriptTest do
4545
escript: [main_module: :escripttest],
4646
deps: [{:ok, path: fixture_path("deps_status/deps/ok")}]]
4747
end
48+
49+
def application do
50+
[applications: []]
51+
end
4852
end
4953

5054
defmodule EscriptWithUnknownMainModule do

0 commit comments

Comments
 (0)