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