Skip to content

Commit d057311

Browse files
author
José Valim
committed
Add --preload-modules to mix app.start
It preloads all modules from all loaded applications after all applications are started. This is useful to ensure code loading does not affect code executed by further mix tasks, such as mix test --slowest N. Closes #6626.
1 parent 3010cd3 commit d057311

File tree

4 files changed

+53
-24
lines changed

4 files changed

+53
-24
lines changed

lib/mix/lib/mix/tasks/app.start.ex

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ defmodule Mix.Tasks.App.Start do
3030
* `--force` - forces compilation regardless of compilation times
3131
* `--temporary` - starts the application as temporary
3232
* `--permanent` - starts the application as permanent
33+
* `--preload-modules` - preloads all modules defined in applications
3334
* `--no-compile` - does not compile even if files require compilation
3435
* `--no-protocols` - does not load consolidated protocols
3536
* `--no-archives-check` - does not check archives
@@ -38,11 +39,18 @@ defmodule Mix.Tasks.App.Start do
3839
* `--no-start` - does not start applications after compilation
3940
4041
"""
42+
43+
@switches [
44+
permanent: :boolean,
45+
temporary: :boolean,
46+
preload_modules: :boolean
47+
]
48+
4149
def run(args) do
4250
Mix.Project.get!()
4351
config = Mix.Project.config()
4452

45-
{opts, _, _} = OptionParser.parse(args, switches: [permanent: :boolean, temporary: :boolean])
53+
{opts, _, _} = OptionParser.parse(args, switches: @switches)
4654
Mix.Task.run("loadpaths", args)
4755

4856
unless "--no-compile" in args do
@@ -76,6 +84,13 @@ defmodule Mix.Tasks.App.Start do
7684
end
7785
else
7886
start(Mix.Project.config(), opts)
87+
88+
# If there is a build path, we will let the application
89+
# that owns the build path do the actual check
90+
unless config[:build_path] do
91+
loaded = loaded_applications(opts)
92+
check_configured(loaded)
93+
end
7994
end
8095

8196
:ok
@@ -97,13 +112,6 @@ defmodule Mix.Tasks.App.Start do
97112

98113
type = type(config, opts)
99114
Enum.each(apps, &ensure_all_started(&1, type))
100-
101-
# If there is a build path, we will let the application
102-
# that owns the build path do the actual check
103-
unless config[:build_path] do
104-
check_configured()
105-
end
106-
107115
:ok
108116
end
109117

@@ -146,25 +154,35 @@ defmodule Mix.Tasks.App.Start do
146154
end
147155
end
148156

149-
defp check_configured() do
157+
defp loaded_applications(opts) do
158+
preload_modules? = opts[:preload_modules]
159+
160+
for {app, _, _} <- Application.loaded_applications() do
161+
if modules = preload_modules? && Application.spec(app, :modules) do
162+
:code.ensure_modules_loaded(modules)
163+
end
164+
165+
app
166+
end
167+
end
168+
169+
defp check_configured(loaded) do
150170
configured = Mix.ProjectStack.configured_applications()
151-
loaded = for {app, _, _} <- Application.loaded_applications(), do: app
152171

153-
_ =
154-
for app <- configured -- loaded, :code.lib_dir(app) == {:error, :bad_name} do
155-
Mix.shell().error("""
156-
You have configured application #{inspect(app)} in your configuration
157-
file, but the application is not available.
172+
for app <- configured -- loaded, :code.lib_dir(app) == {:error, :bad_name} do
173+
Mix.shell().error("""
174+
You have configured application #{inspect(app)} in your configuration file,
175+
but the application is not available.
158176
159-
This usually means one of:
177+
This usually means one of:
160178
161179
1. You have not added the application as a dependency in a mix.exs file.
162180
163181
2. You are configuring an application that does not really exist.
164182
165-
Please ensure #{inspect(app)} exists or remove the configuration.
166-
""")
167-
end
183+
Please ensure #{inspect(app)} exists or remove the configuration.
184+
""")
185+
end
168186

169187
:ok
170188
end

lib/mix/lib/mix/tasks/run.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ defmodule Mix.Tasks.Run do
4040
* `--eval`, `-e` - evaluate the given code
4141
* `--require`, `-r` - requires pattern before running the command
4242
* `--parallel`, `-p` - makes all requires parallel
43+
* `--preload-modules` - preloads all modules defined in applications
4344
* `--no-compile` - does not compile even if files require compilation
4445
* `--no-deps-check` - does not check dependencies
4546
* `--no-archives-check` - does not check archives
@@ -67,7 +68,8 @@ defmodule Mix.Tasks.Run do
6768
start: :boolean,
6869
archives_check: :boolean,
6970
elixir_version_check: :boolean,
70-
parallel_require: :keep
71+
parallel_require: :keep,
72+
preload_modules: :boolean
7173
]
7274
)
7375

lib/mix/lib/mix/tasks/test.ex

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,12 @@ defmodule Mix.Tasks.Test do
6767
* `--no-elixir-version-check` - does not check the Elixir version from mix.exs
6868
* `--no-start` - does not start applications after compilation
6969
* `--only` - runs only tests that match the filter
70+
* `--preload-modules` - preloads all modules defined in applications
7071
* `--raise` - raises if the test suite failed
7172
* `--seed` - seeds the random number generator used to randomize tests order;
7273
`--seed 0` disables randomization
73-
* `--slowest` - prints timing information for the N slowest tests; automatically
74-
sets `--trace`
74+
* `--slowest` - prints timing information for the N slowest tests
75+
Automatically sets `--trace` and `--preload-modules`
7576
* `--stale` - runs only tests which reference modules that changed since the
7677
last `test --stale`. You can read more about this option in the "Stale" section below.
7778
* `--timeout` - sets the timeout for the tests
@@ -190,7 +191,8 @@ defmodule Mix.Tasks.Test do
190191
stale: :boolean,
191192
listen_on_stdin: :boolean,
192193
formatter: :keep,
193-
slowest: :integer
194+
slowest: :integer,
195+
preload_modules: :boolean
194196
]
195197

196198
@cover [output: "cover", tool: Cover]
@@ -235,7 +237,8 @@ defmodule Mix.Tasks.Test do
235237
# available in test_helper.exs. Then configure exunit again so
236238
# that command line options override test_helper.exs
237239
Mix.shell().print_app
238-
Mix.Task.run("app.start", args)
240+
app_start_args = if opts[:slowest], do: ["--preload-modules" | args], else: args
241+
Mix.Task.run("app.start", app_start_args)
239242

240243
# Ensure ExUnit is loaded.
241244
case Application.load(:ex_unit) do

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,18 @@ defmodule Mix.Tasks.App.StartTest do
3535
assert File.regular?("_build/dev/lib/app_start_sample/ebin/Elixir.A.beam")
3636
assert File.regular?("_build/dev/lib/app_start_sample/ebin/app_start_sample.app")
3737

38+
assert :code.is_loaded(A)
3839
refute List.keyfind(Application.started_applications(), :app_start_sample, 0)
3940
assert List.keyfind(Application.started_applications(), :logger, 0)
41+
:code.delete(A)
4042

4143
Mix.Tasks.App.Start.run([])
44+
refute :code.is_loaded(A)
4245
assert List.keyfind(Application.started_applications(), :app_start_sample, 0)
4346
assert List.keyfind(Application.started_applications(), :logger, 0)
47+
48+
Mix.Tasks.App.Start.run(["--preload-modules"])
49+
assert :code.is_loaded(A)
4450
end
4551
end
4652

0 commit comments

Comments
 (0)