Skip to content

Commit

Permalink
Purge project apps on recompile
Browse files Browse the repository at this point in the history
we rely on application controller being able to return app modules
elixir loads apps only on initial compilation and and without purging we end up with outdated module list
workaround elixir-lang/elixir#13001
  • Loading branch information
lukaszsamson committed Oct 9, 2023
1 parent 4c616ad commit d9cc23a
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 28 deletions.
67 changes: 42 additions & 25 deletions apps/language_server/lib/language_server/build.ex
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,14 @@ defmodule ElixirLS.LanguageServer.Build do
end

defp purge_app(app) do
Logger.debug("Stopping #{app}")

case Application.stop(app) do
:ok -> :ok
{:error, {:not_started, _}} -> :ok
{:error, error} -> Logger.warning("Application.stop failed for #{app}: #{inspect(error)}")
end

modules =
case :application.get_key(app, :modules) do
{:ok, modules} -> modules
Expand All @@ -310,31 +318,11 @@ defmodule ElixirLS.LanguageServer.Build do

Logger.debug("Unloading #{app}")

case Application.stop(app) do
:ok -> :ok
{:error, :not_started} -> :ok
{:error, error} -> Logger.warning("Application.stop failed for #{app}: #{inspect(error)}")
end

lib_dir = :code.lib_dir(app)

case Application.unload(app) do
:ok -> :ok
{:error, {:not_loaded, _}} -> :ok
{:error, error} -> Logger.warning("Application.unload failed for #{app}: #{inspect(error)}")
end

if is_list(lib_dir) do
case :code.del_path(:filename.join(lib_dir, ~c"ebin")) do
true ->
:ok

false ->
:ok

{:error, reason} ->
Logger.warning("Unable to clean code path for #{app}: #{inspect(reason)}")
end
end
end

defp get_deps_by_app(deps), do: get_deps_by_app(deps, %{})
Expand Down Expand Up @@ -396,16 +384,46 @@ defmodule ElixirLS.LanguageServer.Build do
cached_deps_by_app = get_deps_by_app(cached_deps)
removed_apps = Map.keys(cached_deps_by_app) -- Map.keys(current_deps_by_app)

removed_deps = cached_deps_by_app |> Map.take(removed_apps)
removed_deps =
cached_deps_by_app
|> Map.take(removed_apps)
|> Enum.flat_map(&elem(&1, 1))
|> Enum.uniq()

for {_app, deps} <- removed_deps,
dep <- deps do
# purge removed dependencies
for dep <- removed_deps do
purge_dep(dep)
end

# purge current dependencies in invalid state
for dep <- current_deps do
maybe_purge_dep(dep)
end

mix_project_apps =
if Mix.Project.umbrella?() do
Mix.Project.apps_paths() |> Enum.map(&elem(&1, 0))
else
# in umbrella Mix.Project.apps_paths() returns nil
# get app from config instead
[Mix.Project.config()[:app]]
end

mix_project_apps_deps =
current_deps_by_app
|> Map.take(mix_project_apps)
|> Enum.flat_map(&elem(&1, 1))
|> Enum.uniq()

# purge mix project apps
# elixir compiler loads apps only on initial compilation
# on subsequent ones it does not update application controller state
# if we don't purge the apps we end up with invalid state
# e.g. :application.get_key(app, :modules) returns outdated module list
# see https://github.com/elixir-lang/elixir/issues/13001
for dep <- mix_project_apps_deps do
purge_dep(dep)
end
end

defp fetch_deps(current_deps) do
Expand Down Expand Up @@ -462,7 +480,6 @@ defmodule ElixirLS.LanguageServer.Build do
options =
if Version.match?(System.version(), ">= 1.14.0") do
Keyword.merge(options,
# we are running the server with consolidated protocols
# this disables warnings `X has already been consolidated`
# when running `compile` task
ignore_already_consolidated: true
Expand Down
12 changes: 9 additions & 3 deletions apps/language_server/lib/language_server/doc_links.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,29 @@ defmodule ElixirLS.LanguageServer.DocLinks do
case get_app(module) do
{app, vsn} ->
"#{@hex_base_url}/#{app}/#{vsn}/#{inspect(module)}.html"
nil -> nil

nil ->
nil
end
end

def hex_docs_function_link(module, function, arity) do
case get_app(module) do
{app, vsn} ->
"#{@hex_base_url}/#{app}/#{vsn}/#{inspect(module)}.html##{function}/#{arity}"
nil -> nil

nil ->
nil
end
end

def hex_docs_type_link(module, type, arity) do
case get_app(module) do
{app, vsn} ->
"#{@hex_base_url}/#{app}/#{vsn}/#{inspect(module)}.html#t:#{type}/#{arity}"
nil -> nil

nil ->
nil
end
end
end
3 changes: 3 additions & 0 deletions apps/language_server/lib/language_server/providers/hover.ex
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ defmodule ElixirLS.LanguageServer.Providers.Hover do
defp build_module_link(module) do
if ElixirSense.Core.Introspection.elixir_module?(module) do
url = DocLinks.hex_docs_module_link(module)

if url do
"[View on hexdocs](#{url})\n\n"
else
Expand All @@ -66,6 +67,7 @@ defmodule ElixirLS.LanguageServer.Providers.Hover do
defp build_function_link(module, function, arity) do
if ElixirSense.Core.Introspection.elixir_module?(module) do
url = DocLinks.hex_docs_function_link(module, function, arity)

if url do
"[View on hexdocs](#{url})\n\n"
else
Expand All @@ -79,6 +81,7 @@ defmodule ElixirLS.LanguageServer.Providers.Hover do
defp build_type_link(module, type, arity) do
if module != nil and ElixirSense.Core.Introspection.elixir_module?(module) do
url = DocLinks.hex_docs_type_link(module, type, arity)

if url do
"[View on hexdocs](#{url})\n\n"
else
Expand Down

0 comments on commit d9cc23a

Please sign in to comment.