Fork of daniellemaywood.uk/gleam — Wasm codegen work
5.8 kB
173 lines
1#!/usr/bin/env escript
2-mode(compile).
3
4% TODO: Don't concurrently print warnings and errors
5% TODO: Some tests
6
7main(_) ->
8 ok = io:setopts([binary, {encoding, utf8}]),
9 ok = configure_logging(),
10 compile_package_loop().
11
12compile_package_loop() ->
13 case io:get_line("") of
14 eof -> ok;
15 Line ->
16 Chars = unicode:characters_to_list(Line),
17 {ok, Tokens, _} = erl_scan:string(Chars),
18 {ok, {Lib, Out, Modules}} = erl_parse:parse_term(Tokens),
19 case compile_package(Lib, Out, Modules) of
20 ok -> io:put_chars("gleam-compile-result-ok\n");
21 err -> io:put_chars("gleam-compile-result-error\n")
22 end,
23 compile_package_loop()
24 end.
25
26compile_package(Lib, Out, Modules) ->
27 IsElixirModule = fun(Module) ->
28 filename:extension(Module) =:= ".ex"
29 end,
30 {ElixirModules, ErlangModules} = lists:partition(IsElixirModule, Modules),
31 ok = filelib:ensure_dir([Out, $/]),
32 ok = add_lib_to_erlang_path(Lib),
33 {ErlangOk, _ErlangBeams} = compile_erlang(ErlangModules, Out),
34 {ElixirOk, _ElixirBeams} = case ErlangOk of
35 true -> compile_elixir(ElixirModules, Out);
36 false -> {false, []}
37 end,
38 ok = del_lib_from_erlang_path(Lib),
39 case ErlangOk and ElixirOk of
40 true -> ok;
41 false -> err
42 end.
43
44compile_erlang(Modules, Out) ->
45 Workers = start_compiler_workers(Out),
46 ok = producer_loop(Modules, Workers),
47 collect_results({true, []}).
48
49collect_results(Acc = {Result, Beams}) ->
50 receive
51 {compiled, Beam} -> collect_results({Result, [Beam | Beams]});
52 failed -> collect_results({false, Beams})
53 after 0 -> Acc
54 end.
55
56producer_loop([], 0) ->
57 ok;
58producer_loop([], Workers) ->
59 receive
60 {work_please, _} -> producer_loop([], Workers - 1)
61 end;
62producer_loop([Module | Modules], Workers) ->
63 receive
64 {work_please, Worker} ->
65 erlang:send(Worker, {module, Module}),
66 producer_loop(Modules, Workers)
67 end.
68
69start_compiler_workers(Out) ->
70 Parent = self(),
71 NumSchedulers = erlang:system_info(schedulers),
72 SpawnWorker = fun(_) ->
73 erlang:spawn_link(fun() -> worker_loop(Parent, Out) end)
74 end,
75 lists:foreach(SpawnWorker, lists:seq(1, NumSchedulers)),
76 NumSchedulers.
77
78worker_loop(Parent, Out) ->
79 Options = [report_errors, report_warnings, debug_info, {outdir, Out}],
80 erlang:send(Parent, {work_please, self()}),
81 receive
82 {module, Module} ->
83 log({compiling, Module}),
84 case compile:file(Module, Options) of
85 {ok, ModuleName} ->
86 Beam = filename:join(Out, ModuleName) ++ ".beam",
87 Message = {compiled, Beam},
88 log(Message),
89 erlang:send(Parent, Message);
90 error ->
91 log({failed, Module}),
92 erlang:send(Parent, failed)
93 end,
94 worker_loop(Parent, Out)
95 end.
96
97compile_elixir(Modules, Out) ->
98 Error = [
99 "The program elixir was not found. Is it installed?",
100 $\n,
101 "Documentation for installing Elixir can be viewed here:",
102 $\n,
103 "https://elixir-lang.org/install.html"
104 ],
105 case Modules of
106 [] -> {true, []};
107 _ ->
108 log({starting, "compiler.app"}),
109 ok = application:start(compiler),
110 log({starting, "elixir.app"}),
111 case application:start(elixir) of
112 ok -> do_compile_elixir(Modules, Out);
113 _ ->
114 io:put_chars(standard_error, [Error, $\n]),
115 {false, []}
116 end
117 end.
118
119do_compile_elixir(Modules, Out) ->
120 ModuleBins = lists:map(fun(Module) ->
121 log({compiling, Module}),
122 list_to_binary(Module)
123 end, Modules),
124 OutBin = list_to_binary(Out),
125 Options = [{dest, OutBin}],
126 % Silence "redefining module" warnings.
127 % Compiled modules in the build directory are added to the code path.
128 % These warnings result from recompiling loaded modules.
129 % TODO: This line can likely be removed if/when the build directory is cleaned before every compilation.
130 'Elixir.Code':compiler_options([{ignore_module_conflict, true}]),
131 case 'Elixir.Kernel.ParallelCompiler':compile_to_path(ModuleBins, OutBin, Options) of
132 {ok, ModuleAtoms, _} ->
133 ToBeam = fun(ModuleAtom) ->
134 Beam = filename:join(Out, atom_to_list(ModuleAtom)) ++ ".beam",
135 log({compiled, Beam}),
136 Beam
137 end,
138 {true, lists:map(ToBeam, ModuleAtoms)};
139 {error, Errors, _} ->
140 % Log all filenames associated with modules that failed to compile.
141 % Note: The compiler prints compilation errors upon encountering them.
142 ErrorFiles = lists:usort([File || {File, _, _} <- Errors]),
143 Log = fun(File) ->
144 log({failed, binary_to_list(File)})
145 end,
146 lists:foreach(Log, ErrorFiles),
147 {false, []};
148 _ -> {false, []}
149 end.
150
151add_lib_to_erlang_path(Lib) ->
152 code:add_paths(expand_lib_paths(Lib)).
153
154-if(?OTP_RELEASE >= 26).
155del_lib_from_erlang_path(Lib) ->
156 code:del_paths(expand_lib_paths(Lib)).
157-else.
158del_lib_from_erlang_path(Lib) ->
159 lists:foreach(fun code:del_path/1, expand_lib_paths(Lib)).
160-endif.
161
162expand_lib_paths(Lib) ->
163 filelib:wildcard([Lib, "/*/ebin"]).
164
165configure_logging() ->
166 Enabled = os:getenv("GLEAM_LOG") /= false,
167 persistent_term:put(gleam_logging_enabled, Enabled).
168
169log(Term) ->
170 case persistent_term:get(gleam_logging_enabled) of
171 true -> io:fwrite("~p~n", [Term]), ok;
172 false -> ok
173 end.