diff --git a/lib/mix/lib/mix/tasks/compile.app.ex b/lib/mix/lib/mix/tasks/compile.app.ex index cca8593d273..768a1de4b9b 100644 --- a/lib/mix/lib/mix/tasks/compile.app.ex +++ b/lib/mix/lib/mix/tasks/compile.app.ex @@ -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 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`. + * `: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` @@ -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 @@ -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() @@ -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 @@ -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}" 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 @@ -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 diff --git a/lib/mix/lib/mix/tasks/deps.ex b/lib/mix/lib/mix/tasks/deps.ex index c885d48b6df..c7ec8be8191 100644 --- a/lib/mix/lib/mix/tasks/deps.ex +++ b/lib/mix/lib/mix/tasks/deps.ex @@ -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 diff --git a/lib/mix/lib/mix/tasks/new.ex b/lib/mix/lib/mix/tasks/new.ex index 3eb92b0c468..a6b8394acd3 100644 --- a/lib/mix/lib/mix/tasks/new.ex +++ b/lib/mix/lib/mix/tasks/new.ex @@ -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 @@ -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 @@ -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, """ @@ -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 @@ -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 diff --git a/lib/mix/test/mix/tasks/app.tree_test.exs b/lib/mix/test/mix/tasks/app.tree_test.exs index 5e74ded9339..7fea640860d 100644 --- a/lib/mix/test/mix/tasks/app.tree_test.exs +++ b/lib/mix/test/mix/tasks/app.tree_test.exs @@ -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 diff --git a/lib/mix/test/mix/tasks/compile.app_test.exs b/lib/mix/test/mix/tasks/compile.app_test.exs index 0b36f7f714f..51d22f6d560 100644 --- a/lib/mix/test/mix/tasks/compile.app_test.exs +++ b/lib/mix/test/mix/tasks/compile.app_test.exs @@ -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 @@ -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 -> @@ -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 @@ -90,6 +124,12 @@ 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 -> @@ -97,19 +137,19 @@ defmodule Mix.Tasks.Compile.AppTest do 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 diff --git a/lib/mix/test/mix/tasks/escript_test.exs b/lib/mix/test/mix/tasks/escript_test.exs index eaafe417266..4e29d3c993c 100644 --- a/lib/mix/test/mix/tasks/escript_test.exs +++ b/lib/mix/test/mix/tasks/escript_test.exs @@ -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