Skip to content

Commit

Permalink
Ensure the remote control node and server use the same Elixir executable
Browse files Browse the repository at this point in the history
Closes #776.
  • Loading branch information
zachallaun committed Jul 19, 2024
1 parent 268c8ff commit ca8f781
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 106 deletions.
86 changes: 0 additions & 86 deletions apps/remote_control/lib/lexical/remote_control.ex
Original file line number Diff line number Diff line change
Expand Up @@ -125,25 +125,6 @@ defmodule Lexical.RemoteControl do
end
end

def elixir_executable(%Project{} = project) do
root_path = Project.root_path(project)

{path_result, env} =
with nil <- version_manager_path_and_env("asdf", root_path),
nil <- version_manager_path_and_env("mise", root_path),
nil <- version_manager_path_and_env("rtx", root_path) do
{File.cd!(root_path, fn -> System.find_executable("elixir") end), System.get_env()}
end

case path_result do
nil ->
{:error, :no_elixir}

executable when is_binary(executable) ->
{:ok, executable, env}
end
end

defp app_globs do
app_globs = Enum.map(@allowed_apps, fn app_name -> "/**/#{app_name}*/ebin" end)
["/**/priv" | app_globs]
Expand All @@ -158,71 +139,4 @@ defmodule Lexical.RemoteControl do
{:error, :epmd_failed}
end
end

defp version_manager_path_and_env(manager, root_path) do
with true <- is_binary(System.find_executable(manager)),
env = reset_env(manager, root_path),
{path, 0} <- System.cmd(manager, ~w(which elixir), cd: root_path, env: env) do
{String.trim(path), env}
else
_ ->
nil
end
end

# We launch lexical by asking the version managers to provide an environment,
# which contains path munging. This initial environment is present in the running
# VM, and needs to be undone so we can find the correct elixir executable in the project.
defp reset_env("asdf", _root_path) do
orig_path = System.get_env("PATH_SAVE", System.get_env("PATH"))

Enum.map(System.get_env(), fn
{"ASDF_ELIXIR_VERSION", _} -> {"ASDF_ELIXIR_VERSION", nil}
{"ASDF_ERLANG_VERSION", _} -> {"ASDF_ERLANG_VERSION", nil}
{"PATH", _} -> {"PATH", orig_path}
other -> other
end)
end

defp reset_env("rtx", root_path) do
{env, _} = System.cmd("rtx", ~w(env -s bash), cd: root_path)

env
|> String.trim()
|> String.split("\n")
|> Enum.map(fn
"export " <> key_and_value ->
[key, value] =
key_and_value
|> String.split("=", parts: 2)
|> Enum.map(&String.trim/1)

{key, value}

_ ->
nil
end)
|> Enum.reject(&is_nil/1)
end

defp reset_env("mise", root_path) do
{env, _} = System.cmd("mise", ~w(env -s bash), cd: root_path)

env
|> String.trim()
|> String.split("\n")
|> Enum.map(fn
"export " <> key_and_value ->
[key, value] =
key_and_value
|> String.split("=", parts: 2)
|> Enum.map(&String.trim/1)

{key, value}

_ ->
nil
end)
|> Enum.reject(&is_nil/1)
end
end
10 changes: 2 additions & 8 deletions apps/remote_control/lib/lexical/remote_control/port.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ defmodule Lexical.RemoteControl.Port do
"""

alias Lexical.Project
alias Lexical.RemoteControl

@type open_opt ::
{:env, list()}
Expand All @@ -22,14 +21,9 @@ defmodule Lexical.RemoteControl.Port do
"""
@spec open_elixir(Project.t(), open_opts()) :: port()
def open_elixir(%Project{} = project, opts) do
{:ok, elixir_executable, environment_variables} = RemoteControl.elixir_executable(project)
opts = Keyword.put_new_lazy(opts, :cd, fn -> Project.root_path(project) end)

opts =
opts
|> Keyword.put_new_lazy(:cd, fn -> Project.root_path(project) end)
|> Keyword.put_new(:env, environment_variables)

open(project, elixir_executable, opts)
open(project, project.elixir_executable, opts)
end

@doc """
Expand Down
2 changes: 1 addition & 1 deletion apps/remote_control/test/support/lexical/test/fixtures.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule Lexical.Test.Fixtures do
|> Path.join()
|> Path.expand()
|> Lexical.Document.Path.to_uri()
|> Project.new()
|> Project.new(elixir_executable: System.find_executable("elixir"))
end

def project do
Expand Down
21 changes: 20 additions & 1 deletion apps/server/lib/lexical/server/boot.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@ defmodule Lexical.Server.Boot do
@target Mix.target()
@dep_apps Enum.map(Mix.Dep.cached(), & &1.app)

def start do
def start(argv) do
{:ok, _} = Application.ensure_all_started(:mix)

Application.stop(:logger)
load_config()
Application.ensure_all_started(:logger)

case fetch_elixir_executable(argv) do
{:ok, exe} ->
Application.put_env(:server, :elixir_executable, exe)

:error ->
halt("FATAL: Lexical must be passed an Elixir executable path on boot")
end

Enum.each(@dep_apps, &load_app_modules/1)

case detect_errors() do
Expand All @@ -42,6 +50,17 @@ defmodule Lexical.Server.Boot do
versioning_errors()
end

defp fetch_elixir_executable(argv) do
if exe = get_elixir_executable(argv) do
{:ok, exe}
else
:error
end
end

defp get_elixir_executable([maybe_exe | _]), do: System.find_executable(maybe_exe)
defp get_elixir_executable([]), do: nil

defp load_config do
config = read_config("config.exs")
runtime = read_config("runtime.exs")
Expand Down
3 changes: 2 additions & 1 deletion apps/server/lib/lexical/server/configuration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ defmodule Lexical.Server.Configuration do
@spec new(Lexical.uri(), map(), String.t() | nil) :: t
def new(root_uri, %ClientCapabilities{} = client_capabilities, client_name) do
support = Support.new(client_capabilities)
project = Project.new(root_uri)
elixir_executable = Application.get_env(:server, :elixir_executable)
project = Project.new(root_uri, elixir_executable: elixir_executable)

%__MODULE__{support: support, project: project, client_name: client_name}
|> tap(&set/1)
Expand Down
2 changes: 1 addition & 1 deletion bin/boot.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ end)
|> Code.append_path()
end)

LXical.Server.Boot.start()
LXical.Server.Boot.start(System.argv())

if System.get_env("LX_HALT_AFTER_BOOT") do
require Logger
Expand Down
3 changes: 2 additions & 1 deletion bin/start_lexical.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ esac
$elixir_command \
--cookie "lexical" \
--no-halt \
"$script_dir/boot.exs"
"$script_dir/boot.exs" \
"$(which $elixir_command)"
21 changes: 14 additions & 7 deletions projects/lexical_shared/lib/lexical/project.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ defmodule Lexical.Project do
mix_target: nil,
env_variables: %{},
project_module: nil,
entropy: 1
entropy: 1,
elixir_executable: nil

@type message :: String.t()
@type restart_notification :: {:restart, Logger.level(), String.t()}
Expand All @@ -30,14 +31,15 @@ defmodule Lexical.Project do

@workspace_directory_name ".lexical"

# Public
@spec new(Lexical.uri()) :: t
def new(root_uri) do
@spec new(Lexical.uri(), attrs :: keyword()) :: t
def new(root_uri, attrs \\ []) do
entropy = :rand.uniform(65_536)

%__MODULE__{entropy: entropy}
__MODULE__
|> struct!([entropy: entropy] ++ attrs)
|> maybe_set_root_uri(root_uri)
|> maybe_set_mix_exs_uri()
|> maybe_set_elixir_executable()
end

@spec set_project_module(t(), module() | nil) :: t()
Expand Down Expand Up @@ -237,8 +239,7 @@ defmodule Lexical.Project do
end
end

defp maybe_set_root_uri(%__MODULE__{} = project, nil),
do: %__MODULE__{project | root_uri: nil}
defp maybe_set_root_uri(%__MODULE__{} = project, nil), do: project

defp maybe_set_root_uri(%__MODULE__{} = project, "file://" <> _ = root_uri) do
root_path =
Expand Down Expand Up @@ -271,6 +272,12 @@ defmodule Lexical.Project do
end
end

defp maybe_set_elixir_executable(%__MODULE__{elixir_executable: nil} = project) do
%__MODULE__{project | elixir_executable: System.find_executable("elixir")}
end

defp maybe_set_elixir_executable(%__MODULE__{} = project), do: project

# Project Path

# Environment variables
Expand Down

0 comments on commit ca8f781

Please sign in to comment.