Skip to content

Commit f5dbeba

Browse files
committed
fix: add to PATH based on installed tool versions
1 parent ba94052 commit f5dbeba

File tree

2 files changed

+54
-107
lines changed

2 files changed

+54
-107
lines changed

apps/expert/lib/expert/engine_node.ex

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,9 @@ defmodule Expert.EngineNode do
171171
# Expert release, and we build it on the fly for the project elixir+opt
172172
# versions if it was not built yet.
173173
defp glob_paths(%Project{} = project) do
174-
{:ok, erl, _env} = Expert.Port.erlang_executable(project)
175174
{:ok, elixir, env} = Expert.Port.elixir_executable(project)
176175

177176
expert_priv = :code.priv_dir(:expert)
178-
root_path = Project.root_path(project)
179177
packaged_engine_source = Path.join([expert_priv, "engine_source", "apps", "engine"])
180178

181179
engine_source =
@@ -185,8 +183,6 @@ defmodule Expert.EngineNode do
185183

186184
build_engine_script = Path.join(expert_priv, "build_engine.exs")
187185

188-
:ok = patch_versions_file(root_path, engine_source, erl, elixir)
189-
190186
opts =
191187
[
192188
:stderr_to_stdout,
@@ -242,70 +238,6 @@ defmodule Expert.EngineNode do
242238
|> Path.join("lib/**/ebin")
243239
|> Path.wildcard()
244240
end
245-
246-
defp patch_versions_file(root_path, engine_source, erl, elixir) do
247-
{elixir_version, 0} = System.cmd(elixir, ["--short-version"], cd: root_path)
248-
249-
{erl_version, 0} =
250-
System.cmd(
251-
erl,
252-
[
253-
"-eval",
254-
"{ok, Version} = file:read_file(filename:join([code:root_dir(), \"releases\", erlang:system_info(otp_release), \"OTP_VERSION\"])), io:fwrite(Version), halt().",
255-
"-noshell"
256-
],
257-
cd: root_path
258-
)
259-
260-
versions_path = Path.join([engine_source, ".tool-versions"])
261-
262-
overrides = %{
263-
"elixir" => String.trim(elixir_version),
264-
"erlang" => String.trim(erl_version)
265-
}
266-
267-
with true <- File.exists?(versions_path),
268-
{:ok, versions} <- read_versions(versions_path) do
269-
updated = Map.merge(versions, overrides)
270-
content = versions_to_iodata(updated)
271-
272-
File.write(versions_path, content)
273-
else
274-
false ->
275-
content = versions_to_iodata(overrides)
276-
277-
File.write(versions_path, content)
278-
279-
error ->
280-
error
281-
end
282-
end
283-
284-
defp versions_to_iodata(versions) do
285-
Enum.map(versions, fn {lang, version} -> [lang, " ", version, "\n"] end)
286-
end
287-
288-
defp read_versions(path) do
289-
case File.read(path) do
290-
{:ok, content} ->
291-
versions =
292-
content
293-
|> String.split("\n")
294-
|> Enum.map(&String.trim/1)
295-
|> Enum.reject(&(&1 == ""))
296-
|> Enum.reduce(%{}, fn line, acc ->
297-
case String.split(line) do
298-
[lang, version] -> Map.put(acc, lang, version)
299-
[] -> acc
300-
end
301-
end)
302-
303-
{:ok, versions}
304-
305-
{:error, _reason} = error ->
306-
error
307-
end
308-
end
309241
end
310242

311243
@stop_timeout 1_000

apps/expert/lib/expert/port.ex

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -32,36 +32,28 @@ defmodule Expert.Port do
3232
end
3333

3434
def elixir_executable(%Project{} = project) do
35-
find_executable(project, "elixir")
36-
end
37-
38-
def erlang_executable(%Project{} = project) do
39-
find_executable(project, "erl")
40-
end
41-
42-
defp find_executable(project, exe) do
4335
root_path = Project.root_path(project)
4436

4537
{path_result, env} =
46-
with nil <- version_manager_path_and_env("asdf", exe, root_path),
47-
nil <- version_manager_path_and_env("mise", exe, root_path),
48-
nil <- version_manager_path_and_env("rtx", exe, root_path) do
49-
{File.cd!(root_path, fn -> System.find_executable(exe) end), System.get_env()}
38+
with nil <- version_manager_path_and_env("asdf", root_path),
39+
nil <- version_manager_path_and_env("mise", root_path),
40+
nil <- version_manager_path_and_env("rtx", root_path) do
41+
{File.cd!(root_path, fn -> System.find_executable("elixir") end), System.get_env()}
5042
end
5143

5244
case path_result do
5345
nil ->
54-
{:error, :"#{exe}_not_found"}
46+
{:error, :no_elixir}
5547

5648
executable when is_binary(executable) ->
5749
{:ok, executable, env}
5850
end
5951
end
6052

61-
defp version_manager_path_and_env(manager, exe, root_path) do
53+
defp version_manager_path_and_env(manager, root_path) do
6254
with true <- is_binary(System.find_executable(manager)),
63-
env = reset_env(manager, exe, root_path),
64-
{path, 0} <- System.cmd(manager, ["which", exe], cd: root_path, env: env) do
55+
env = reset_env(manager, root_path),
56+
{path, 0} <- System.cmd(manager, ~w(which elixir), cd: root_path, env: env) do
6557
{String.trim(path), env}
6658
else
6759
_ ->
@@ -72,36 +64,26 @@ defmodule Expert.Port do
7264
# We launch expert by asking the version managers to provide an environment,
7365
# which contains path munging. This initial environment is present in the running
7466
# VM, and needs to be undone so we can find the correct elixir executable in the project.
75-
defp reset_env("asdf", exe, root_path) do
76-
{env, _} = System.cmd("asdf", ["env", exe], cd: root_path)
77-
78-
env =
79-
env
80-
|> String.trim()
81-
|> String.split("\n")
82-
|> Enum.map(fn key_and_value ->
83-
[key, value] =
84-
key_and_value
85-
|> String.split("=", parts: 2)
86-
|> Enum.map(&String.trim/1)
67+
defp reset_env("asdf", root_path) do
68+
data_dir = System.get_env("ASDF_DATA_DIR") || Path.join(System.user_home!(), ".asdf")
69+
installs_dir = Path.join(data_dir, "installs")
8770

88-
{key, value}
89-
end)
90-
|> Enum.reject(&is_nil/1)
71+
{current, 0} = System.cmd("asdf", ["list"], cd: root_path)
9172

92-
asdf_path =
93-
case List.keyfind(env, "ASDF_INSTALL_PATH", 0) do
94-
{_, path} -> Path.join(path, "../../../shims")
95-
_ -> ""
96-
end
73+
versions = parse_asdf_installed_versions(current, %{})
74+
75+
installed_bin_paths =
76+
versions
77+
|> Enum.map(fn {tool, version} -> Path.join([installs_dir, tool, version, "bin"]) end)
78+
|> Enum.join(":")
9779

9880
Enum.map(System.get_env(), fn
99-
{"PATH", path} -> {"PATH", "#{asdf_path}:#{path}"}
81+
{"PATH", path} -> {"PATH", "#{installed_bin_paths}:#{path}"}
10082
other -> other
10183
end)
10284
end
10385

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

10789
env
@@ -126,7 +108,7 @@ defmodule Expert.Port do
126108
|> Enum.reject(&is_nil/1)
127109
end
128110

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

132114
env
@@ -151,6 +133,39 @@ defmodule Expert.Port do
151133
|> Enum.reject(&is_nil/1)
152134
end
153135

136+
defp parse_asdf_installed_versions(output, acc) when is_binary(output) do
137+
output
138+
|> String.split("\n", trim: true)
139+
|> parse_asdf_installed_versions(acc)
140+
end
141+
142+
defp parse_asdf_installed_versions([], acc), do: acc
143+
144+
defp parse_asdf_installed_versions([line | rest], acc) do
145+
tool_fn? = &(not String.starts_with?(&1, " "))
146+
147+
case tool_fn?.(line) do
148+
true ->
149+
{versions, rest} = Enum.split_while(rest, &(not tool_fn?.(&1)))
150+
versions = Enum.map(versions, &String.trim/1)
151+
version = Enum.find(versions, &String.starts_with?(&1, "*"))
152+
153+
case version do
154+
nil ->
155+
parse_asdf_installed_versions(rest, acc)
156+
157+
version when is_binary(version) ->
158+
version = String.trim(version, "*")
159+
acc = Map.put(acc, line, version)
160+
161+
parse_asdf_installed_versions(rest, acc)
162+
end
163+
164+
false ->
165+
parse_asdf_installed_versions(rest, acc)
166+
end
167+
end
168+
154169
@doc """
155170
Launches an executable in the project context via a port.
156171
"""

0 commit comments

Comments
 (0)