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