Skip to content

Commit

Permalink
Add --preload-modules to mix app.start
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
José Valim committed Dec 23, 2017
1 parent e79b59b commit 4ea903b
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 24 deletions.
56 changes: 37 additions & 19 deletions lib/mix/lib/mix/tasks/app.start.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ defmodule Mix.Tasks.App.Start do
* `--force` - forces compilation regardless of compilation times
* `--temporary` - starts the application as temporary
* `--permanent` - starts the application as permanent
* `--preload-modules` - preloads all modules defined in applications
* `--no-compile` - does not compile even if files require compilation
* `--no-protocols` - does not load consolidated protocols
* `--no-archives-check` - does not check archives
Expand All @@ -38,11 +39,18 @@ defmodule Mix.Tasks.App.Start do
* `--no-start` - does not start applications after compilation
"""

@switches [
permanent: :boolean,
temporary: :boolean,
preload_modules: :boolean
]

def run(args) do
Mix.Project.get!()
config = Mix.Project.config()

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

unless "--no-compile" in args do
Expand Down Expand Up @@ -76,6 +84,13 @@ defmodule Mix.Tasks.App.Start do
end
else
start(Mix.Project.config(), opts)

# If there is a build path, we will let the application
# that owns the build path do the actual check
unless config[:build_path] do
loaded = loaded_applications(opts)
check_configured(loaded)
end
end

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

type = type(config, opts)
Enum.each(apps, &ensure_all_started(&1, type))

# If there is a build path, we will let the application
# that owns the build path do the actual check
unless config[:build_path] do
check_configured()
end

:ok
end

Expand Down Expand Up @@ -146,25 +154,35 @@ defmodule Mix.Tasks.App.Start do
end
end

defp check_configured() do
defp loaded_applications(opts) do
preload_modules? = opts[:preload_modules]

for {app, _, _} <- Application.loaded_applications() do
if modules = preload_modules? && Application.spec(app, :modules) do
:code.ensure_modules_loaded(modules)
end

app
end
end

defp check_configured(loaded) do
configured = Mix.ProjectStack.configured_applications()
loaded = for {app, _, _} <- Application.loaded_applications(), do: app

_ =
for app <- configured -- loaded, :code.lib_dir(app) == {:error, :bad_name} do
Mix.shell().error("""
You have configured application #{inspect(app)} in your configuration
file, but the application is not available.
for app <- configured -- loaded, :code.lib_dir(app) == {:error, :bad_name} do
Mix.shell().error("""
You have configured application #{inspect(app)} in your configuration file,
but the application is not available.
This usually means one of:
This usually means one of:
1. You have not added the application as a dependency in a mix.exs file.
2. You are configuring an application that does not really exist.
Please ensure #{inspect(app)} exists or remove the configuration.
""")
end
Please ensure #{inspect(app)} exists or remove the configuration.
""")
end

:ok
end
Expand Down
4 changes: 3 additions & 1 deletion lib/mix/lib/mix/tasks/run.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ defmodule Mix.Tasks.Run do
* `--eval`, `-e` - evaluate the given code
* `--require`, `-r` - requires pattern before running the command
* `--parallel`, `-p` - makes all requires parallel
* `--preload-modules` - preloads all modules defined in applications
* `--no-compile` - does not compile even if files require compilation
* `--no-deps-check` - does not check dependencies
* `--no-archives-check` - does not check archives
Expand Down Expand Up @@ -67,7 +68,8 @@ defmodule Mix.Tasks.Run do
start: :boolean,
archives_check: :boolean,
elixir_version_check: :boolean,
parallel_require: :keep
parallel_require: :keep,
preload_modules: :boolean
]
)

Expand Down
11 changes: 7 additions & 4 deletions lib/mix/lib/mix/tasks/test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ defmodule Mix.Tasks.Test do
* `--no-elixir-version-check` - does not check the Elixir version from mix.exs
* `--no-start` - does not start applications after compilation
* `--only` - runs only tests that match the filter
* `--preload-modules` - preloads all modules defined in applications
* `--raise` - raises if the test suite failed
* `--seed` - seeds the random number generator used to randomize tests order;
`--seed 0` disables randomization
* `--slowest` - prints timing information for the N slowest tests; automatically
sets `--trace`
* `--slowest` - prints timing information for the N slowest tests
Automatically sets `--trace` and `--preload-modules`
* `--stale` - runs only tests which reference modules that changed since the
last `test --stale`. You can read more about this option in the "Stale" section below.
* `--timeout` - sets the timeout for the tests
Expand Down Expand Up @@ -190,7 +191,8 @@ defmodule Mix.Tasks.Test do
stale: :boolean,
listen_on_stdin: :boolean,
formatter: :keep,
slowest: :integer
slowest: :integer,
preload_modules: :boolean
]

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

# Ensure ExUnit is loaded.
case Application.load(:ex_unit) do
Expand Down
6 changes: 6 additions & 0 deletions lib/mix/test/mix/tasks/app.start_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ defmodule Mix.Tasks.App.StartTest do
assert File.regular?("_build/dev/lib/app_start_sample/ebin/Elixir.A.beam")
assert File.regular?("_build/dev/lib/app_start_sample/ebin/app_start_sample.app")

assert :code.is_loaded(A)
refute List.keyfind(Application.started_applications(), :app_start_sample, 0)
assert List.keyfind(Application.started_applications(), :logger, 0)
:code.delete(A)

Mix.Tasks.App.Start.run([])
refute :code.is_loaded(A)
assert List.keyfind(Application.started_applications(), :app_start_sample, 0)
assert List.keyfind(Application.started_applications(), :logger, 0)

Mix.Tasks.App.Start.run(["--preload-modules"])
assert :code.is_loaded(A)
end
end

Expand Down

0 comments on commit 4ea903b

Please sign in to comment.