Files
n8n-openai-adapter/lib/elixir/src/elixir_compiler.erl
T
Or Neeman ce60061bb9 Improve message for compilation error writing .beam
Closes #3164

This commit improves the error message that results when compilation of
a file fails when trying to write the .beam file.  An example is the
one from the issue, in which the user does not have write access to the
file.
2015-04-28 09:13:35 -06:00

281 lines
8.8 KiB
Erlang

-module(elixir_compiler).
-export([get_opt/1, string/2, quoted/2, file/1, file/2, file_to_path/2]).
-export([core/0, module/4, eval_forms/3]).
-include("elixir.hrl").
%% Public API
get_opt(Key) ->
Dict = elixir_config:get(compiler_options),
case lists:keyfind(Key, 1, Dict) of
false -> false;
{Key, Value} -> Value
end.
%% Compilation entry points.
string(Contents, File) when is_list(Contents), is_binary(File) ->
string(Contents, File, nil).
string(Contents, File, Dest) ->
Forms = elixir:'string_to_quoted!'(Contents, 1, File, []),
quoted(Forms, File, Dest).
quoted(Forms, File) when is_binary(File) ->
quoted(Forms, File, nil).
quoted(Forms, File, Dest) ->
Previous = get(elixir_compiled),
try
put(elixir_compiled, []),
elixir_lexical:run(File, Dest, fun
(Pid) ->
Env = elixir:env_for_eval([{line, 1}, {file, File}]),
eval_forms(Forms, [], Env#{lexical_tracker := Pid})
end),
lists:reverse(get(elixir_compiled))
after
put(elixir_compiled, Previous)
end.
file(Relative) when is_binary(Relative) ->
file(Relative, nil).
file(Relative, Dest) ->
File = filename:absname(Relative),
{ok, Bin} = file:read_file(File),
string(elixir_utils:characters_to_list(Bin), File, case Dest of
nil -> Dest;
_ -> filename:absname(Dest)
end).
file_to_path(File, Dest) when is_binary(File), is_binary(Dest) ->
Comp = file(File, Dest),
Abs = filename:absname(Dest),
_ = [binary_to_path(X, Abs) || X <- Comp],
Comp.
%% Evaluation
eval_forms(Forms, Vars, E) ->
case (?m(E, module) == nil) andalso allows_fast_compilation(Forms) of
true -> eval_compilation(Forms, Vars, E);
false -> code_loading_compilation(Forms, Vars, E)
end.
eval_compilation(Forms, Vars, E) ->
Binding = [{Key, Value} || {_Name, _Kind, Key, Value} <- Vars],
{Result, _Binding, EE, _S} = elixir:eval_forms(Forms, Binding, E),
{Result, EE}.
code_loading_compilation(Forms, Vars, #{line := Line} = E) ->
Dict = [{{Name, Kind}, {Value, 0}} || {Name, Kind, Value, _} <- Vars],
S = elixir_env:env_to_scope_with_vars(E, Dict),
{Expr, EE, _S} = elixir:quoted_to_erl(Forms, E, S),
{Module, I} = retrieve_module_name(),
Fun = code_fun(?m(E, module)),
Form = code_mod(Fun, Expr, Line, ?m(E, file), Module, Vars),
Args = list_to_tuple([V || {_, _, _, V} <- Vars]),
%% Pass {native, false} to speed up bootstrap
%% process when native is set to true
AllOpts = options(),
FinalOpts = AllOpts -- [native, warn_missing_spec],
inner_module(Form, FinalOpts, true, E, fun(_, Binary) ->
%% If we have labeled locals, anonymous functions
%% were created and therefore we cannot ditch the
%% module
Purgeable =
case beam_lib:chunks(Binary, [labeled_locals]) of
{ok, {_, [{labeled_locals, []}]}} -> true;
_ -> false
end,
dispatch_loaded(Module, Fun, Args, Purgeable, I, EE)
end).
options() ->
case elixir_config:get(erl_compiler_options) of
nil ->
elixir_config:update(erl_compiler_options, fun options/1);
Opts ->
Opts
end.
options(nil) ->
Key = "ERL_COMPILER_OPTIONS",
case os:getenv(Key) of
false -> [];
Str when is_list(Str) ->
case erl_scan:string(Str) of
{ok, Tokens, _} ->
case erl_parse:parse_term(Tokens ++ [{dot, 1}]) of
{ok, List} when is_list(List) -> List;
{ok, Term} -> [Term];
{error, _Reason} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end;
{error, {_, _, _Reason}, _} ->
io:format("Ignoring bad term in ~ts\n", [Key]),
[]
end
end;
options(Opts) ->
Opts.
dispatch_loaded(Module, Fun, Args, Purgeable, I, E) ->
Res = Module:Fun(Args),
code:delete(Module),
if Purgeable ->
code:purge(Module),
return_module_name(I);
true ->
ok
end,
{Res, E}.
code_fun(nil) -> '__FILE__';
code_fun(_) -> '__MODULE__'.
code_mod(Fun, Expr, Line, File, Module, Vars) when is_binary(File), is_integer(Line) ->
Tuple = {tuple, Line, [{var, Line, K} || {_, _, K, _} <- Vars]},
Relative = elixir_utils:relative_to_cwd(File),
[
{attribute, Line, file, {elixir_utils:characters_to_list(Relative), 1}},
{attribute, Line, module, Module},
{attribute, Line, export, [{Fun, 1}, {'__RELATIVE__', 0}]},
{function, Line, Fun, 1, [
{clause, Line, [Tuple], [], [Expr]}
]},
{function, Line, '__RELATIVE__', 0, [
{clause, Line, [], [], [elixir_utils:elixir_to_erl(Relative)]}
]}
].
retrieve_module_name() ->
elixir_code_server:call(retrieve_module_name).
return_module_name(I) ->
elixir_code_server:cast({return_module_name, I}).
allows_fast_compilation({'__block__', _, Exprs}) ->
lists:all(fun allows_fast_compilation/1, Exprs);
allows_fast_compilation({defmodule, _, _}) -> true;
allows_fast_compilation(_) -> false.
%% INTERNAL API
%% Compile the module by forms based on the scope information
%% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module.
module(Forms, Opts, E, Callback) ->
Final =
case (get_opt(debug_info) == true) orelse
lists:member(debug_info, Opts) of
true -> [debug_info] ++ options();
false -> options()
end,
inner_module(Forms, Final, false, E, Callback).
inner_module(Forms, Options, Bootstrap, #{file := File} = E, Callback) when
is_list(Forms), is_list(Options), is_boolean(Bootstrap), is_function(Callback) ->
Source = elixir_utils:characters_to_list(File),
case compile:noenv_forms([no_auto_import()|Forms], [return, {source, Source}|Options]) of
{ok, Module, Binary, Warnings} ->
format_warnings(Bootstrap, Warnings),
{module, Module} = code:load_binary(Module, beam_location(E), Binary),
Callback(Module, Binary);
{error, Errors, Warnings} ->
format_warnings(Bootstrap, Warnings),
format_errors(Errors)
end.
beam_location(#{module := nil}) -> in_memory;
beam_location(#{lexical_tracker := Pid, module := Module}) ->
case elixir_lexical:dest(Pid) of
nil -> in_memory;
Dest ->
filename:join(elixir_utils:characters_to_list(Dest),
atom_to_list(Module) ++ ".beam")
end.
no_auto_import() ->
{attribute, 0, compile, no_auto_import}.
%% CORE HANDLING
core() ->
{ok, _} = application:ensure_all_started(elixir),
New = orddict:from_list([{docs, false}, {internal, true}]),
Merge = fun(_, _, Value) -> Value end,
Update = fun(Old) -> orddict:merge(Merge, Old, New) end,
_ = elixir_config:update(compiler_options, Update),
[core_file(File) || File <- core_main()].
core_file(File) ->
try
Lists = file(File),
_ = [binary_to_path(X, "lib/elixir/ebin") || X <- Lists],
io:format("Compiled ~ts~n", [File])
catch
Kind:Reason ->
io:format("~p: ~p~nstacktrace: ~p~n", [Kind, Reason, erlang:get_stacktrace()]),
erlang:halt(1)
end.
core_main() ->
[<<"lib/elixir/lib/kernel.ex">>,
<<"lib/elixir/lib/macro/env.ex">>,
<<"lib/elixir/lib/keyword.ex">>,
<<"lib/elixir/lib/module.ex">>,
<<"lib/elixir/lib/list.ex">>,
<<"lib/elixir/lib/macro.ex">>,
<<"lib/elixir/lib/code.ex">>,
<<"lib/elixir/lib/module/locals_tracker.ex">>,
<<"lib/elixir/lib/kernel/def.ex">>,
<<"lib/elixir/lib/kernel/typespec.ex">>,
<<"lib/elixir/lib/behaviour.ex">>,
<<"lib/elixir/lib/exception.ex">>,
<<"lib/elixir/lib/protocol.ex">>,
<<"lib/elixir/lib/stream/reducers.ex">>,
<<"lib/elixir/lib/enum.ex">>,
<<"lib/elixir/lib/inspect/algebra.ex">>,
<<"lib/elixir/lib/inspect.ex">>,
<<"lib/elixir/lib/range.ex">>,
<<"lib/elixir/lib/regex.ex">>,
<<"lib/elixir/lib/string.ex">>,
<<"lib/elixir/lib/string/chars.ex">>,
<<"lib/elixir/lib/io.ex">>,
<<"lib/elixir/lib/path.ex">>,
<<"lib/elixir/lib/file.ex">>,
<<"lib/elixir/lib/system.ex">>,
<<"lib/elixir/lib/kernel/cli.ex">>,
<<"lib/elixir/lib/kernel/error_handler.ex">>,
<<"lib/elixir/lib/kernel/parallel_compiler.ex">>,
<<"lib/elixir/lib/kernel/lexical_tracker.ex">>].
binary_to_path({ModuleName, Binary}, CompilePath) ->
Path = filename:join(CompilePath, atom_to_list(ModuleName) ++ ".beam"),
case file:write_file(Path, Binary) of
ok -> Path;
{error, Reason} -> error('Elixir.File.Error':exception([{action, "write to"}, {path, Path}, {reason, Reason}]))
end.
%% ERROR HANDLING
format_errors([]) ->
exit({nocompile, "compilation failed but no error was raised"});
format_errors(Errors) ->
lists:foreach(fun ({File, Each}) ->
BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun(Error) -> elixir_errors:handle_file_error(BinFile, Error) end, Each)
end, Errors).
format_warnings(Bootstrap, Warnings) ->
lists:foreach(fun ({File, Each}) ->
BinFile = elixir_utils:characters_to_binary(File),
lists:foreach(fun(Warning) -> elixir_errors:handle_file_warning(Bootstrap, BinFile, Warning) end, Each)
end, Warnings).