Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 38 additions & 20 deletions lib/mix/lib/mix/tasks/compile.app.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ defmodule Mix.Tasks.Compile.App do
function in your `mix.exs` with the following options:

* `:applications` - all applications your application depends
on at runtime. For example, if your application depends on
Erlang's `:crypto`, it needs to be added to this list. Most
of your dependencies must be added as well (unless they're
a development or test dependency). Mix and other tools use this
list in order to properly boot your application dependencies
on at runtime. By default, this list is automatically inflected
from your dependencies. Any extra Erlang/Elixir dependency
must be specified in `:extra_applications`. Mix and other tools
use the application list in order to start your dependencies
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing final period.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other items on the list do not have a final period.

before starting the application itself.

* `:extra_applications` - a list of Erlang/Elixir applications
that you want started before your application. For example,
Elixir's `:logger` or Erlang's `:crypto`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other items on the list do not have a final period.

Then no final period here 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On this list there is a final period on the items. I am just keeping it consistent with the surrounding lists (although Elixir is not consistent).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, alrighty sorry for the noise then!


* `:registered` - the name of all registered processes in the
application. If your application defines a local GenServer
with name `MyServer`, it is recommended to add `MyServer`
Expand Down Expand Up @@ -79,10 +82,10 @@ defmodule Mix.Tasks.Compile.App do

if opts[:force] || Mix.Utils.stale?(sources, [target]) || modules_changed?(mods, target) do
best_guess = [
vsn: to_charlist(version),
description: to_charlist(config[:description] || app),
modules: mods,
applications: [],
registered: [],
vsn: to_charlist(version),
]

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

properties = ensure_correct_properties(app, config, properties)

# Ensure we always prepend the standard application dependencies
properties = Keyword.update!(properties, :applications, fn apps ->
[:kernel, :stdlib] ++ language_app(config) ++ apps
end)

properties = ensure_correct_properties(properties, config)
contents = :io_lib.format("~p.~n", [{:application, app, properties}])

Mix.Project.ensure_structure()
Expand Down Expand Up @@ -152,13 +149,14 @@ defmodule Mix.Tasks.Compile.App do
end
end

defp ensure_correct_properties(app, config, properties) do
defp ensure_correct_properties(properties, config) do
properties
|> Keyword.put_new(:description, to_charlist(config[:description] || app))
|> validate_properties
|> validate_properties!
|> Keyword.put_new_lazy(:applications, fn -> apps_from_prod_non_optional_deps(properties) end)
|> Keyword.update!(:applications, fn apps -> normalize_apps(apps, properties, config) end)
end

defp validate_properties(properties) do
defp validate_properties!(properties) do
Enum.each properties, fn
{:description, value} ->
unless is_list(value) do
Expand Down Expand Up @@ -188,13 +186,17 @@ defmodule Mix.Tasks.Compile.App do
unless is_list(value) and Enum.all?(value, &is_atom(&1)) do
Mix.raise "Application included applications (:included_applications) should be a list of atoms, got: #{inspect value}"
end
{:extra_applications, value} ->
unless is_list(value) and Enum.all?(value, &is_atom(&1)) do
Mix.raise "Application extra applications (:extra_applications) should be a list of atoms, got: #{inspect value}"
end
{:applications, value} ->
unless is_list(value) and Enum.all?(value, &is_atom(&1)) do
Mix.raise "Application dependencies (:applications) should be a list of atoms, got: #{inspect value}"
Mix.raise "Application applications (:applications) should be a list of atoms, got: #{inspect value}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Application applications"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And above "Application extra applications".

end
{:env, value} ->
unless Keyword.keyword?(value) do
Mix.raise "Application dependencies (:env) should be a keyword list, got: #{inspect value}"
Mix.raise "Application environment (:env) should be a keyword list, got: #{inspect value}"
end
{:start_phases, value} ->
unless Keyword.keyword?(value) do
Expand All @@ -212,4 +214,20 @@ defmodule Mix.Tasks.Compile.App do

properties
end

defp apps_from_prod_non_optional_deps(properties) do
included_applications = Keyword.get(properties, :included_applications, [])

for %{app: app, opts: opts, top_level: true} <- Mix.Dep.cached,
Keyword.get(opts, :app, true),
Keyword.get(opts, :runtime, true),
not Keyword.get(opts, :optional, false),
not app in included_applications,
do: app
end

defp normalize_apps(apps, properties, config) do
extra = Keyword.get(properties, :extra_applications, [])
Enum.uniq([:kernel, :stdlib] ++ language_app(config) ++ extra ++ apps)
end
end
4 changes: 4 additions & 0 deletions lib/mix/lib/mix/tasks/deps.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ defmodule Mix.Tasks.Deps do
try to infer the type of project but it can be overridden with this
option by setting it to `:mix`, `:rebar`, `:rebar3` or `:make`

* `:runtime` - whether the dependency is part of runtime applications.
Defaults to `true` which automatically adds the application to the list
of apps that are started automatically and included in releases

### Git options (`:git`)

* `:git` - the Git repository URI
Expand Down
35 changes: 14 additions & 21 deletions lib/mix/lib/mix/tasks/new.ex
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ defmodule Mix.Tasks.New do
end

defp otp_app(_mod, false) do
" [applications: [:logger]]"
" [extra_applications: [:logger]]"
end

defp otp_app(mod, true) do
" [applications: [:logger],\n mod: {#{mod}.Application, []}]"
" [extra_applications: [:logger],\n mod: {#{mod}.Application, []}]"
end

defp cd_path(".") do
Expand Down Expand Up @@ -218,23 +218,14 @@ defmodule Mix.Tasks.New do
<%= if @app do %>
## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `<%= @app %>` to your list of dependencies in `mix.exs`:

1. Add `<%= @app %>` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[{:<%= @app %>, "~> 0.1.0"}]
end
```

2. Ensure `<%= @app %>` is started before your application:

```elixir
def application do
[applications: [:<%= @app %>]]
end
```
```elixir
def deps do
[{:<%= @app %>, "~> 0.1.0"}]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
Expand All @@ -255,14 +246,14 @@ defmodule Mix.Tasks.New do
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
"""

embed_template :mixfile, """
Expand All @@ -282,6 +273,7 @@ defmodule Mix.Tasks.New do
#
# Type "mix help compile.app" for more information
def application do
# Specify extra applications you'll use from Erlang/Elixir
<%= @otp_app %>
end

Expand Down Expand Up @@ -321,6 +313,7 @@ defmodule Mix.Tasks.New do
#
# Type "mix help compile.app" for more information
def application do
# Specify extra applications you'll use from Erlang/Elixir
<%= @otp_app %>
end

Expand Down
5 changes: 2 additions & 3 deletions lib/mix/test/mix/tasks/app.tree_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,9 @@ defmodule Mix.Tasks.App.TreeTest do
in_fixture "umbrella_dep/deps/umbrella", fn ->
Mix.Project.in_project(:umbrella, ".", fn _ ->
Mix.Task.run "app.tree", ["--format", "pretty"]
assert_received {:mix_shell, :info, ["├── elixir"]}
assert_received {:mix_shell, :info, ["foo"]}
assert_received {:mix_shell, :info, ["└── elixir"]}
assert_received {:mix_shell, :info, ["bar"]}
assert_received {:mix_shell, :info, ["└── elixir"]}
assert_received {:mix_shell, :info, [" └── elixir"]}
end)
end
end
Expand Down
52 changes: 46 additions & 6 deletions lib/mix/test/mix/tasks/compile.app_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,30 @@ defmodule Mix.Tasks.Compile.AppTest do

def application do
[maxT: :infinity,
applications: [:example_app]]
applications: [:example_app],
extra_applications: [:logger]]
end
end

defmodule CustomDeps do
def project do
[app: :custom_deps, version: "0.2.0", deps: deps()]
end

def application do
[extra_applications: [:logger], included_applications: [:ok9]]
end

def deps do
[{:ok1, path: "../ok"},
{:ok2, path: "../ok", only: :prod},
{:ok3, path: "../ok", only: :dev},
{:ok4, path: "../ok", runtime: true},
{:ok5, path: "../ok", runtime: false},
{:ok6, path: "../ok", optional: true},
{:ok7, path: "../ok", optional: false},
{:ok8, path: "../ok", app: false},
{:ok9, path: "../ok"}]
end
end

Expand Down Expand Up @@ -48,7 +71,7 @@ defmodule Mix.Tasks.Compile.AppTest do
end
end

test "use custom application settings" do
test "uses custom application settings" do
Mix.Project.push CustomProject

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

test "automatically inflects applications" do
Mix.Project.push CustomDeps

in_fixture "no_mixfile", fn ->
Mix.Tasks.Compile.Elixir.run([])
Mix.Tasks.Compile.App.run([])
contents = File.read!("_build/dev/lib/custom_deps/ebin/custom_deps.app")
assert contents =~ "{applications,[kernel,stdlib,elixir,logger,ok1,ok3,ok4,ok7]}"
end
end

test "application properties validation" do
Mix.Project.push InvalidProject

Expand Down Expand Up @@ -90,26 +124,32 @@ defmodule Mix.Tasks.Compile.AppTest do
Mix.Tasks.Compile.App.run([])
end

Process.put(:application, [extra_applications: ["invalid"]])
message = "Application extra applications (:extra_applications) should be a list of atoms, got: [\"invalid\"]"
assert_raise Mix.Error, message, fn ->
Mix.Tasks.Compile.App.run([])
end

Process.put(:application, [included_applications: ["invalid"]])
message = "Application included applications (:included_applications) should be a list of atoms, got: [\"invalid\"]"
assert_raise Mix.Error, message, fn ->
Mix.Tasks.Compile.App.run([])
end

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

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

Process.put(:application, [env: [:invalid]])
message = "Application dependencies (:env) should be a keyword list, got: [:invalid]"
message = "Application environment (:env) should be a keyword list, got: [:invalid]"
assert_raise Mix.Error, message, fn ->
Mix.Tasks.Compile.App.run([])
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a test here for validation of :extra_applications as well? We have it for all other options, it may be weird to skip this one. 😕

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

Expand Down
4 changes: 4 additions & 0 deletions lib/mix/test/mix/tasks/escript_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ defmodule Mix.Tasks.EscriptTest do
escript: [main_module: :escripttest],
deps: [{:ok, path: fixture_path("deps_status/deps/ok")}]]
end

def application do
[applications: []]
end
end

defmodule EscriptWithUnknownMainModule do
Expand Down