Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure the remote control node and server use the same Elixir executable #795

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
20 changes: 19 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,16 @@ defmodule Lexical.Server.Boot do
versioning_errors()
end

defp fetch_elixir_executable(argv) do
case get_elixir_executable(argv) do
nil -> :error
exe -> {:ok, exe}
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)"
1 change: 0 additions & 1 deletion integration/boot/set_up_mise.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ chmod +x ./mise
eval "$(./mise activate bash)"

export KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac --without-termcap --without-wx"
./mise plugin install -y erlang
./mise use --global "erlang@$ERLANG_VERSION"

./mise plugins install -y elixir
Expand Down
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
Loading