You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

232 lines
8.1 KiB

15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
  1. %% -------------------------------------------------------------------
  2. %%
  3. %% rebar: Erlang Build Tools
  4. %%
  5. %% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
  6. %%
  7. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  8. %% of this software and associated documentation files (the "Software"), to deal
  9. %% in the Software without restriction, including without limitation the rights
  10. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. %% copies of the Software, and to permit persons to whom the Software is
  12. %% furnished to do so, subject to the following conditions:
  13. %%
  14. %% The above copyright notice and this permission notice shall be included in
  15. %% all copies or substantial portions of the Software.
  16. %%
  17. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. %% THE SOFTWARE.
  24. %% -------------------------------------------------------------------
  25. -module(rebar_erlc_compiler).
  26. -export([compile/2,
  27. clean/2]).
  28. %% make available for rebar_eunit until there is a better option
  29. -export([do_compile/8, compile_opts/2, list_hrls/2]).
  30. -include("rebar.hrl").
  31. %% ===================================================================
  32. %% Public API
  33. %% ===================================================================
  34. compile(Config, _AppFile) ->
  35. do_compile(Config, "src/*.erl", "ebin", ".erl", ".beam",
  36. fun list_hrls/2, fun compile_erl/2,
  37. rebar_config:get_list(Config, erl_first_files, [])),
  38. do_compile(Config, "mibs/*.mib", "priv/mibs", ".mib", ".bin",
  39. undefined, fun compile_mib/2,
  40. rebar_config:get_list(Config, mib_first_files, [])).
  41. clean(_Config, _AppFile) ->
  42. %% TODO: This would be more portable if it used Erlang to traverse
  43. %% the dir structure and delete each file; however it would also
  44. %% much slower.
  45. [] = os:cmd("rm -f ebin/*.beam priv/mibs/*.bin"),
  46. ok.
  47. %% ===================================================================
  48. %% Internal functions
  49. %% ===================================================================
  50. do_compile(Config, SrcWildcard, OutDir, InExt, OutExt,
  51. IncludeFn, CompileFn, FirstFiles) ->
  52. case filelib:wildcard(SrcWildcard) of
  53. [] ->
  54. ok;
  55. FoundFiles when is_list(FoundFiles) ->
  56. %% Ensure that the FirstFiles are compiled first; drop them from the
  57. %% FoundFiles and compile them in sequence
  58. FirstTargets = [{Fs, target_file(Fs, OutDir, InExt, OutExt)} || Fs <- FirstFiles],
  59. RestTargets = [{Fs, target_file(Fs, OutDir, InExt, OutExt)} ||
  60. Fs <- drop_each(FirstFiles, FoundFiles)],
  61. %% Make sure target directory exists
  62. ok = filelib:ensure_dir(target_file(hd(FoundFiles), OutDir, InExt, OutExt)),
  63. %% Compile first targets in sequence
  64. compile_each(FirstTargets, Config, IncludeFn, CompileFn),
  65. %% Spin up workers
  66. Self = self(),
  67. Pids = [spawn_monitor(fun() -> compile_worker(Self) end) || _I <- lists:seq(1,3)],
  68. %% Process rest of targets
  69. compile_queue(Pids, RestTargets, Config, IncludeFn, CompileFn)
  70. end.
  71. drop_each([], List) ->
  72. List;
  73. drop_each([Member | Rest], List) ->
  74. drop_each(Rest, lists:delete(Member, List)).
  75. compile_each([], _Config, _IncludeFn, _CompileFn) ->
  76. ok;
  77. compile_each([{Src, Target} | Rest], Config, IncludeFn, CompileFn) ->
  78. case needs_compile(Src, Target, IncludeFn, Config) of
  79. true ->
  80. ?CONSOLE("Compiling ~s\n", [Src]),
  81. CompileFn(Src, Config);
  82. false ->
  83. ?INFO("Skipping ~s\n", [Src]),
  84. ok
  85. end,
  86. compile_each(Rest, Config, IncludeFn, CompileFn).
  87. needs_compile(Src, Target, IncludeFn, Config) ->
  88. TargetLM = filelib:last_modified(Target),
  89. case TargetLM < filelib:last_modified(Src) of
  90. true ->
  91. true;
  92. false ->
  93. if is_function(IncludeFn) ->
  94. lists:any(fun(I) ->
  95. TargetLM < filelib:last_modified(I)
  96. end,
  97. IncludeFn(Src, Config));
  98. true ->
  99. false
  100. end
  101. end.
  102. target_file(F, TargetDir, InExt, OutExt) ->
  103. filename:join([TargetDir, filename:basename(F, InExt) ++ OutExt]).
  104. compile_opts(Config, Key) ->
  105. rebar_config:get_list(Config, Key, []).
  106. list_hrls(Src, Config) ->
  107. case epp:open(Src, include_path(Src, Config)) of
  108. {ok, Epp} ->
  109. %% check include for erlang files
  110. extract_includes(Epp, Src);
  111. _ ->
  112. false
  113. end.
  114. include_path(Src, Config) ->
  115. [filename:dirname(Src)|compile_opts(Config, i)].
  116. extract_includes(Epp, Src) ->
  117. case epp:parse_erl_form(Epp) of
  118. {ok, {attribute, 1, file, {Src, 1}}} ->
  119. extract_includes(Epp, Src);
  120. {ok, {attribute, 1, file, {IncFile, 1}}} ->
  121. [IncFile|extract_includes(Epp, Src)];
  122. {ok, _} ->
  123. extract_includes(Epp, Src);
  124. {eof, _} ->
  125. epp:close(Epp),
  126. [];
  127. {error, _Error} ->
  128. extract_includes(Epp, Src)
  129. end.
  130. compile_erl(Source, Config) ->
  131. Opts = [{i, "include"}, {outdir, "ebin"}, report, return] ++ compile_opts(Config, erl_opts),
  132. case compile:file(Source, Opts) of
  133. {ok, _, []} ->
  134. ok;
  135. {ok, _, _Warnings} ->
  136. %% We got at least one warning -- if fail_on_warning is in options, fail
  137. case lists:member(fail_on_warning, Opts) of
  138. true ->
  139. ?FAIL;
  140. false ->
  141. ok
  142. end;
  143. _ ->
  144. ?FAIL
  145. end.
  146. compile_mib(Source, Config) ->
  147. Opts = [{outdir, "priv/mibs"}, {i, ["priv/mibs"]}] ++ compile_opts(Config, mib_opts),
  148. case snmpc:compile(Source, Opts) of
  149. {ok, _} ->
  150. ok;
  151. {error, compilation_failed} ->
  152. ?FAIL
  153. end.
  154. compile_queue([], [], _Config, _IncludeFn, _CompileFn) ->
  155. ok;
  156. compile_queue(Pids, Targets, Config, IncludeFn, CompileFn) ->
  157. receive
  158. {next, Worker} ->
  159. case Targets of
  160. [] ->
  161. Worker ! empty,
  162. compile_queue(Pids, Targets, Config, IncludeFn, CompileFn);
  163. [{Src, Target} | Rest] ->
  164. Worker ! {compile, Src, Target, Config, IncludeFn, CompileFn},
  165. compile_queue(Pids, Rest, Config, IncludeFn, CompileFn)
  166. end;
  167. {fail, Error} ->
  168. ?DEBUG("Worker compilation failed: ~p\n", [Error]),
  169. ?FAIL;
  170. {compiled, Source} ->
  171. ?CONSOLE("Compiled ~s\n", [Source]),
  172. compile_queue(Pids, Targets, Config, IncludeFn, CompileFn);
  173. {'DOWN', Mref, _, Pid, normal} ->
  174. ?DEBUG("Worker exited cleanly\n", []),
  175. Pids2 = lists:delete({Pid, Mref}, Pids),
  176. compile_queue(Pids2, Targets, Config, IncludeFn, CompileFn);
  177. {'DOWN', _Mref, _, _Pid, Info} ->
  178. ?DEBUG("Worker failed: ~p\n", [Info]),
  179. ?FAIL
  180. end.
  181. compile_worker(QueuePid) ->
  182. QueuePid ! {next, self()},
  183. receive
  184. {compile, Src, Target, Config, IncludeFn, CompileFn} ->
  185. case needs_compile(Src, Target, IncludeFn, Config) of
  186. true ->
  187. case catch(CompileFn(Src, Config)) of
  188. ok ->
  189. QueuePid ! {compiled, Src},
  190. compile_worker(QueuePid);
  191. Error ->
  192. QueuePid ! {fail, Error},
  193. ok
  194. end;
  195. false ->
  196. ?INFO("Skipping ~s\n", [Src]),
  197. compile_worker(QueuePid)
  198. end;
  199. empty ->
  200. ok
  201. end.