Skip to content

Commit a7258fe

Browse files
committed
Ensure --no-optional-deps skips them even after they are compiled, closes elixir-lang#14856
1 parent a8000f2 commit a7258fe

File tree

5 files changed

+75
-60
lines changed

5 files changed

+75
-60
lines changed

lib/mix/lib/mix/tasks/compile.all.ex

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ defmodule Mix.Tasks.Compile.All do
3737
# the load paths before compiling, which means any SCM coming
3838
# from archives will be removed from the code path.
3939
deps = Mix.Dep.cached()
40-
apps = project_apps(config)
40+
apps = compile_apps(config, args)
4141

4242
{loaded_paths, loaded_modules} =
4343
Mix.AppLoader.load_apps(apps, deps, config, {[], []}, fn {app, path}, {paths, mods} ->
@@ -97,7 +97,10 @@ defmodule Mix.Tasks.Compile.All do
9797
{status, diagnostics}
9898
end
9999

100-
def project_apps(config) do
100+
@doc """
101+
Returns all apps that should be available at compile time.
102+
"""
103+
def compile_apps(config, args) do
101104
project = Mix.Project.get!()
102105

103106
properties =
@@ -107,12 +110,17 @@ defmodule Mix.Tasks.Compile.All do
107110
Keyword.get(properties, :included_applications, []) ++
108111
Keyword.get(properties, :extra_applications, [])
109112

110-
{all, _optional} =
111-
Mix.Tasks.Compile.App.project_apps(properties, config, extra, fn ->
112-
# Include all deps by design
113-
Enum.map(config[:deps], &elem(&1, 0))
113+
{all, optional} =
114+
Mix.Utils.project_apps(properties, config, extra, fn apps_opts ->
115+
for {app, opts} <- apps_opts do
116+
{app, if(Keyword.get(opts, :optional, false), do: :optional, else: :required)}
117+
end
114118
end)
115119

116-
all
120+
if "--no-optional-deps" in args do
121+
all -- optional
122+
else
123+
all
124+
end
117125
end
118126
end

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

Lines changed: 4 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -447,19 +447,19 @@ defmodule Mix.Tasks.Compile.App do
447447
end
448448

449449
{all, optional} =
450-
project_apps(properties, config, extra, fn ->
451-
apps_from_runtime_prod_deps(properties, config)
450+
Mix.Utils.project_apps(properties, config, extra, fn apps_opts ->
451+
apps_from_runtime_prod_deps(properties, apps_opts)
452452
end)
453453

454454
properties
455455
|> Keyword.put(:applications, all)
456456
|> Keyword.put(:optional_applications, optional)
457457
end
458458

459-
defp apps_from_runtime_prod_deps(properties, config) do
459+
defp apps_from_runtime_prod_deps(properties, apps_opts) do
460460
included_applications = Keyword.get(properties, :included_applications, [])
461461

462-
for {app, opts} <- deps_opts(config),
462+
for {app, opts} <- apps_opts,
463463
runtime_app?(opts),
464464
app not in included_applications,
465465
do: {app, if(Keyword.get(opts, :optional, false), do: :optional, else: :required)}
@@ -483,44 +483,4 @@ defmodule Mix.Tasks.Compile.App do
483483
:error -> true
484484
end
485485
end
486-
487-
defp deps_opts(config) do
488-
for config_dep <- Keyword.get(config, :deps, []),
489-
do: {elem(config_dep, 0), dep_opts(config_dep)}
490-
end
491-
492-
defp dep_opts({_app, opts}) when is_list(opts), do: opts
493-
defp dep_opts({_app, _req, opts}) when is_list(opts), do: opts
494-
defp dep_opts(_), do: []
495-
496-
## Helpers for loading and manipulating apps
497-
498-
@doc false
499-
def project_apps(properties, config, extra, deps_loader) do
500-
apps = Keyword.get(properties, :applications) || deps_loader.()
501-
{all, required, optional} = split_by_type(extra ++ apps)
502-
all = Enum.uniq(language_apps(config) ++ Enum.reverse(all))
503-
optional = Enum.uniq(Enum.reverse(optional -- required))
504-
{all, optional}
505-
end
506-
507-
defp split_by_type(apps) do
508-
Enum.reduce(apps, {[], [], []}, fn
509-
app, {all, required, optional} when is_atom(app) ->
510-
{[app | all], [app | required], optional}
511-
512-
{app, :required}, {all, required, optional} ->
513-
{[app | all], [app | required], optional}
514-
515-
{app, :optional}, {all, required, optional} ->
516-
{[app | all], required, [app | optional]}
517-
end)
518-
end
519-
520-
defp language_apps(config) do
521-
case Keyword.get(config, :language, :elixir) do
522-
:elixir -> [:kernel, :stdlib, :elixir]
523-
:erlang -> [:kernel, :stdlib]
524-
end
525-
end
526486
end

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ defmodule Mix.Tasks.Compile do
155155
# If we are in an umbrella project, now load paths from all children.
156156
if apps_paths = Mix.Project.apps_paths(config) do
157157
loaded_paths =
158-
(Mix.Tasks.Compile.All.project_apps(config) ++ Map.keys(apps_paths))
158+
(Mix.Tasks.Compile.All.compile_apps(config, args) ++ Map.keys(apps_paths))
159159
|> Mix.AppLoader.load_apps(Mix.Dep.cached(), config, [], fn
160160
{_app, path}, acc -> if path, do: [path | acc], else: acc
161161
end)

lib/mix/lib/mix/utils.ex

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,49 @@ defmodule Mix.Utils do
1111
# For bootstrapping purposes
1212
@compile {:no_warn_undefined, Logger}
1313

14+
@doc """
15+
Returns the apps of a project with no filtering.
16+
17+
`compile.all` calls it to extract all compile-time apps.
18+
`compile.app` calls it to extract all runtime apps.
19+
"""
20+
def project_apps(properties, config, extra, deps_loader) do
21+
apps = Keyword.get(properties, :applications) || deps_loader.(deps_opts(config))
22+
{all, required, optional} = split_by_type(extra ++ apps)
23+
all = Enum.uniq(language_apps(config) ++ Enum.reverse(all))
24+
optional = Enum.uniq(Enum.reverse(optional -- required))
25+
{all, optional}
26+
end
27+
28+
defp split_by_type(apps) do
29+
Enum.reduce(apps, {[], [], []}, fn
30+
app, {all, required, optional} when is_atom(app) ->
31+
{[app | all], [app | required], optional}
32+
33+
{app, :required}, {all, required, optional} ->
34+
{[app | all], [app | required], optional}
35+
36+
{app, :optional}, {all, required, optional} ->
37+
{[app | all], required, [app | optional]}
38+
end)
39+
end
40+
41+
defp language_apps(config) do
42+
case Keyword.get(config, :language, :elixir) do
43+
:elixir -> [:kernel, :stdlib, :elixir]
44+
:erlang -> [:kernel, :stdlib]
45+
end
46+
end
47+
48+
defp deps_opts(config) do
49+
for config_dep <- Keyword.get(config, :deps, []),
50+
do: {elem(config_dep, 0), dep_opts(config_dep)}
51+
end
52+
53+
defp dep_opts({_app, opts}) when is_list(opts), do: opts
54+
defp dep_opts({_app, _req, opts}) when is_list(opts), do: opts
55+
defp dep_opts(_), do: []
56+
1457
@doc """
1558
Gets the Mix home.
1659

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
17021702
test "compiles without optional dependencies" do
17031703
in_fixture("no_mixfile", fn ->
17041704
Mix.Project.push(GitApp)
1705+
Mix.Tasks.Deps.Get.run([])
17051706

17061707
File.write!("lib/a.ex", """
17071708
defmodule A do
@@ -1712,15 +1713,18 @@ defmodule Mix.Tasks.Compile.ElixirTest do
17121713
assert capture_io(:stderr, fn ->
17131714
Mix.Tasks.Compile.run(["--no-optional-deps"]) == :ok
17141715
end) =~ "GitRepo.hello/0 is undefined"
1715-
end)
1716-
end
17171716

1718-
test "recompiles if --no-optional-deps change" do
1719-
in_fixture("no_mixfile", fn ->
1720-
Mix.Project.push(MixTest.Case.Sample)
1721-
assert Mix.Tasks.Compile.Elixir.run([]) == {:ok, []}
1722-
assert Mix.Tasks.Compile.Elixir.run([]) == {:noop, []}
1723-
assert Mix.Tasks.Compile.Elixir.run(["--no-optional-deps"]) == {:ok, []}
1717+
Mix.Task.clear()
1718+
assert Mix.Tasks.Compile.run([]) == {:ok, []}
1719+
1720+
purge([GitRepo])
1721+
Mix.Task.clear()
1722+
1723+
assert capture_io(:stderr, fn ->
1724+
Mix.Tasks.Compile.run(["--no-optional-deps"]) == :ok
1725+
end) =~ "GitRepo.hello/0 is undefined"
1726+
1727+
refute Code.ensure_loaded?(GitRepo)
17241728
end)
17251729
end
17261730

0 commit comments

Comments
 (0)