Skip to content

Commit

Permalink
Consider :runtime and :app from mix.exs (#8109)
Browse files Browse the repository at this point in the history
Since those values are not stored on Hex, we should not
expect them to be available on Mix.Dep.cached. Instead
we traverse the deps in `mix.exs` to lift this information.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
  • Loading branch information
josevalim authored and José Valim committed Aug 24, 2018
1 parent 28cf40f commit af64fe7
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 51 deletions.
22 changes: 7 additions & 15 deletions lib/mix/lib/mix/dep.ex
Expand Up @@ -117,11 +117,6 @@ defmodule Mix.Dep do
end
end

# optional and runtime only matter at the top level.
# Any non-top level dependency that is optional and
# is still available means it has been fulfilled.
@child_keep_opts [:optional, :runtime]

defp load_and_cache(_config, top, top, env) do
converge(env: env)
end
Expand All @@ -136,21 +131,18 @@ defmodule Mix.Dep do
for dep <- deps,
dep.app == app,
child <- dep.deps,
do: {child.app, Keyword.take(child.opts, @child_keep_opts)},
do: {child.app, Keyword.get(child.opts, :optional, false)},
into: %{}

Enum.map(children, fn %{app: app} = dep ->
Enum.map(children, fn %{app: app, opts: opts} = dep ->
# optional only matters at the top level. Any non-top level dependency
# that is optional and is still available means it has been fulfilled.
case top_level do
%{^app => child_opts} ->
opts =
dep.opts
|> Keyword.drop(@child_keep_opts)
|> Keyword.merge(child_opts)

%{dep | top_level: true, opts: opts}
%{^app => optional} ->
%{dep | top_level: true, opts: Keyword.put(opts, :optional, optional)}

%{} ->
%{dep | top_level: false}
%{dep | top_level: false, opts: Keyword.delete(opts, :optional)}
end
end)
end
Expand Down
23 changes: 19 additions & 4 deletions lib/mix/lib/mix/tasks/compile.app.ex
Expand Up @@ -208,7 +208,7 @@ defmodule Mix.Tasks.Compile.App do
apps =
properties
|> Keyword.get(:applications)
|> Kernel.||(apps_from_prod_non_optional_deps(properties))
|> Kernel.||(apps_from_prod_non_optional_deps(properties, config))
|> normalize_apps(extra, config)

Keyword.put(properties, :applications, apps)
Expand Down Expand Up @@ -313,17 +313,32 @@ defmodule Mix.Tasks.Compile.App do
end)
end

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

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 Map.has_key?(non_runtime_deps, app),
app not in included_applications,
do: app
end

defp non_runtime_deps(config) do
for config_dep <- Keyword.get(config, :deps, []),
not runtime_dep?(config_dep),
do: {elem(config_dep, 0), true},
into: %{}
end

defp runtime_dep?({_app, opts}) when is_list(opts), do: runtime_opts?(opts)
defp runtime_dep?({_app, _req, opts}) when is_list(opts), do: runtime_opts?(opts)
defp runtime_dep?(_), do: true

defp runtime_opts?(opts) do
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true)
end

defp normalize_apps(apps, extra, config) do
Enum.uniq([:kernel, :stdlib] ++ language_app(config) ++ extra ++ apps)
end
Expand Down
8 changes: 6 additions & 2 deletions lib/mix/lib/mix/tasks/deps.ex
Expand Up @@ -93,8 +93,12 @@ defmodule Mix.Tasks.Deps do
and override it by setting the `:override` option in a top-level project.
* `: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
If the `:applications` key is not provided in `def application` in your
mix.exs file, Mix will automatically included all dependencies as a runtime
application, except if `runtime: false` is given. Defaults to true.
* `:system_env` - an enumerable of key-value tuples of binaries to be set
as environment variables when loading or compiling the dependency
### Git options (`:git`)
Expand Down
34 changes: 4 additions & 30 deletions lib/mix/test/mix/dep_test.exs
Expand Up @@ -534,7 +534,7 @@ defmodule Mix.DepTest do
end)
end

test "nested deps with runtime override on parent" do
test "nested deps considers runtime from current app" do
Process.put(:custom_deps_git_repo_opts, runtime: false)

deps = [
Expand All @@ -544,38 +544,12 @@ defmodule Mix.DepTest do

with_deps(deps, fn ->
in_fixture("deps_status", fn ->
File.mkdir_p!("custom/deps_repo/lib")

File.write!("custom/deps_repo/lib/a.ex", """
# Check that the child dependency is top_level and optional
[%Mix.Dep{app: :git_repo, top_level: true, opts: opts}] = Mix.Dep.cached()
false = Keyword.fetch!(opts, :runtime)
""")

Mix.Tasks.Deps.Get.run([])
Mix.Tasks.Deps.Compile.run([])
end)
end)
end

test "nested deps with runtime override on child" do
deps = [
{:deps_repo, "0.1.0", path: "custom/deps_repo"},
{:git_repo, "0.1.0", git: MixTest.Case.fixture_path("git_repo"), runtime: false}
]

with_deps(deps, fn ->
in_fixture("deps_status", fn ->
File.mkdir_p!("custom/deps_repo/lib")

File.write!("custom/deps_repo/lib/a.ex", """
# Check that the child dependency is top_level and optional
[%Mix.Dep{app: :git_repo, top_level: true, opts: opts}] = Mix.Dep.cached()
false = Keyword.has_key?(opts, :runtime)
""")
{:ok, [{:application, :deps_repo, opts}]} =
:file.consult("_build/dev/lib/deps_repo/ebin/deps_repo.app")

Mix.Tasks.Deps.Get.run([])
Mix.Tasks.Deps.Compile.run([])
assert :git_repo not in Keyword.get(opts, :applications)
end)
end)
end
Expand Down

0 comments on commit af64fe7

Please sign in to comment.