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.

284 lines
12 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
15 years ago
15 years ago
  1. %% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. %% -------------------------------------------------------------------
  4. %%
  5. %% rebar: Erlang Build Tools
  6. %%
  7. %% Copyright (c) 2009, 2010 Dave Smith (dizzyd@dizzyd.com)
  8. %%
  9. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  10. %% of this software and associated documentation files (the "Software"), to deal
  11. %% in the Software without restriction, including without limitation the rights
  12. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. %% copies of the Software, and to permit persons to whom the Software is
  14. %% furnished to do so, subject to the following conditions:
  15. %%
  16. %% The above copyright notice and this permission notice shall be included in
  17. %% all copies or substantial portions of the Software.
  18. %%
  19. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. %% THE SOFTWARE.
  26. %% -------------------------------------------------------------------
  27. -module(rebar_erlc_compiler).
  28. -export([compile/2,
  29. clean/2]).
  30. -export([doterl_compile/2,
  31. doterl_compile/3]).
  32. -include("rebar.hrl").
  33. %% ===================================================================
  34. %% Public API
  35. %% ===================================================================
  36. -spec compile(Config::#config{}, AppFile::string()) -> 'ok'.
  37. compile(Config, _AppFile) ->
  38. rebar_base_compiler:run(Config, rebar_config:get_list(Config, yrl_first_files, []),
  39. "src", ".yrl", "src", ".erl",
  40. fun compile_yrl/3),
  41. doterl_compile(Config, "ebin"),
  42. rebar_base_compiler:run(Config, rebar_config:get_list(Config, mib_first_files, []),
  43. "mibs", ".mib", "priv/mibs", ".bin",
  44. fun compile_mib/3).
  45. -spec clean(Config::#config{}, AppFile::string()) -> 'ok'.
  46. clean(_Config, _AppFile) ->
  47. %% TODO: This would be more portable if it used Erlang to traverse
  48. %% the dir structure and delete each file; however it would also
  49. %% much slower.
  50. ok = rebar_file_utils:rm_rf("ebin/*.beam priv/mibs/*.bin"),
  51. YrlFiles = rebar_utils:find_files("src", "^.*\\.yrl\$"),
  52. rebar_file_utils:delete_each(
  53. [ binary_to_list(iolist_to_binary(re:replace(F, "\\.yrl$", ".erl")))
  54. || F <- YrlFiles ]),
  55. %% Erlang compilation is recursive, so it's possible that we have a nested
  56. %% directory structure in ebin with .beam files within. As such, we want
  57. %% to scan whatever is left in the ebin/ directory for sub-dirs which
  58. %% satisfy our criteria.
  59. BeamFiles = rebar_utils:find_files("ebin", "^.*\\.beam\$"),
  60. rebar_file_utils:delete_each(BeamFiles),
  61. lists:foreach(fun(Dir) -> delete_dir(Dir, dirs(Dir)) end, dirs("ebin")),
  62. ok.
  63. %% ===================================================================
  64. %% .erl Compilation API (externally used by only eunit)
  65. %% ===================================================================
  66. -spec doterl_compile(Config::#config{}, OutDir::string()) -> 'ok'.
  67. doterl_compile(Config, OutDir) ->
  68. doterl_compile(Config, OutDir, []).
  69. doterl_compile(Config, OutDir, MoreSources) ->
  70. FirstErls = rebar_config:get_list(Config, erl_first_files, []),
  71. ErlOpts = rebar_config:get(Config, erl_opts, []),
  72. %% Support the src_dirs option allowing multiple directories to
  73. %% contain erlang source. This might be used, for example, should eunit tests be
  74. %% separated from the core application source.
  75. SrcDirs = src_dirs(proplists:append_values(src_dirs, ErlOpts)),
  76. RestErls = [Source || Source <- gather_src(SrcDirs, []) ++ MoreSources,
  77. lists:member(Source, FirstErls) == false],
  78. % Sort RestErls so that parse_transforms and behaviours are first
  79. % This should probably be somewhat combined with inspect_epp
  80. SortedRestErls = [K || {K, _V} <- lists:keysort(2,
  81. [{F, compile_priority(F)} || F <- RestErls ])],
  82. %% Make sure that ebin/ is on the path
  83. CurrPath = code:get_path(),
  84. code:add_path("ebin"),
  85. rebar_base_compiler:run(Config, FirstErls, SortedRestErls,
  86. fun(S, C) -> internal_erl_compile(S, C, OutDir) end),
  87. code:set_path(CurrPath),
  88. ok.
  89. %% ===================================================================
  90. %% Internal functions
  91. %% ===================================================================
  92. -spec include_path(Source::string(), Config::#config{}) -> [string()].
  93. include_path(Source, Config) ->
  94. ErlOpts = rebar_config:get(Config, erl_opts, []),
  95. ["include", filename:dirname(Source)] ++ proplists:get_all_values(i, ErlOpts).
  96. -spec inspect(Source::string(), IncludePath::[string()]) -> {string(), [string()]}.
  97. inspect(Source, IncludePath) ->
  98. ModuleDefault = filename:basename(Source, ".erl"),
  99. case epp:open(Source, IncludePath) of
  100. {ok, Epp} ->
  101. inspect_epp(Epp, Source, ModuleDefault, []);
  102. {error, Reason} ->
  103. ?DEBUG("Failed to inspect ~s: ~p\n", [Source, Reason]),
  104. {ModuleDefault, []}
  105. end.
  106. -spec inspect_epp(Epp::pid(), Source::string(), Module::string(), Includes::[string()]) -> {string(), [string()]}.
  107. inspect_epp(Epp, Source, Module, Includes) ->
  108. case epp:parse_erl_form(Epp) of
  109. {ok, {attribute, _, module, ModInfo}} ->
  110. case ModInfo of
  111. %% Typical module name, single atom
  112. ActualModule when is_atom(ActualModule) ->
  113. ActualModuleStr = atom_to_list(ActualModule);
  114. %% Packag-ized module name, list of atoms
  115. ActualModule when is_list(ActualModule) ->
  116. ActualModuleStr = string:join([atom_to_list(P) || P <- ActualModule], ".");
  117. %% Parameterized module name, single atom
  118. {ActualModule, _} when is_atom(ActualModule) ->
  119. ActualModuleStr = atom_to_list(ActualModule);
  120. %% Parameterized and packagized module name, list of atoms
  121. {ActualModule, _} when is_list(ActualModule) ->
  122. ActualModuleStr = string:join([atom_to_list(P) || P <- ActualModule], ".")
  123. end,
  124. inspect_epp(Epp, Source, ActualModuleStr, Includes);
  125. {ok, {attribute, 1, file, {Module, 1}}} ->
  126. inspect_epp(Epp, Source, Module, Includes);
  127. {ok, {attribute, 1, file, {Source, 1}}} ->
  128. inspect_epp(Epp, Source, Module, Includes);
  129. {ok, {attribute, 1, file, {IncFile, 1}}} ->
  130. inspect_epp(Epp, Source, Module, [IncFile | Includes]);
  131. {eof, _} ->
  132. epp:close(Epp),
  133. {Module, Includes};
  134. _ ->
  135. inspect_epp(Epp, Source, Module, Includes)
  136. end.
  137. -spec needs_compile(Source::string(), Target::string(), Hrls::[string()]) -> boolean().
  138. needs_compile(Source, Target, Hrls) ->
  139. TargetLastMod = filelib:last_modified(Target),
  140. lists:any(fun(I) -> TargetLastMod < filelib:last_modified(I) end,
  141. [Source] ++ Hrls).
  142. -spec internal_erl_compile(Source::string(), Config::#config{}, Outdir::string()) -> 'ok' | 'skipped'.
  143. internal_erl_compile(Source, Config, Outdir) ->
  144. %% Determine the target name and includes list by inspecting the source file
  145. {Module, Hrls} = inspect(Source, include_path(Source, Config)),
  146. %% Construct the target filename
  147. Target = filename:join([Outdir | string:tokens(Module, ".")]) ++ ".beam",
  148. ok = filelib:ensure_dir(Target),
  149. %% If the file needs compilation, based on last mod date of includes or
  150. %% the target,
  151. case needs_compile(Source, Target, Hrls) of
  152. true ->
  153. Opts = [{i, "include"}, {outdir, filename:dirname(Target)}, report, return] ++
  154. rebar_config:get(Config, erl_opts, []),
  155. case compile:file(Source, Opts) of
  156. {ok, _, []} ->
  157. ok;
  158. {ok, _, _Warnings} ->
  159. %% We got at least one warning -- if fail_on_warning is in options, fail
  160. case lists:member(fail_on_warning, Opts) of
  161. true ->
  162. ?FAIL;
  163. false ->
  164. ok
  165. end;
  166. _ ->
  167. ?FAIL
  168. end;
  169. false ->
  170. skipped
  171. end.
  172. -spec compile_mib(Source::string(), Target::string(), Config::#config{}) -> 'ok'.
  173. compile_mib(Source, Target, Config) ->
  174. ok = rebar_utils:ensure_dir(Target),
  175. Opts = [{outdir, "priv/mibs"}, {i, ["priv/mibs"]}] ++
  176. rebar_config:get(Config, mib_opts, []),
  177. case snmpc:compile(Source, Opts) of
  178. {ok, _} ->
  179. ok;
  180. {error, compilation_failed} ->
  181. ?FAIL
  182. end.
  183. -spec compile_yrl(Source::string(), Target::string(), Config::#config{}) -> 'ok'.
  184. compile_yrl(Source, Target, Config) ->
  185. case yrl_needs_compile(Source, Target) of
  186. true ->
  187. Opts = [{parserfile, Target}, {return, true}
  188. |rebar_config:get(Config, yrl_opts, [])],
  189. case yecc:file(Source, Opts) of
  190. {ok, _, []} ->
  191. ok;
  192. {ok, _, _Warnings} ->
  193. case lists:member(fail_on_warnings, Config) of
  194. true ->
  195. ?FAIL;
  196. false ->
  197. ok
  198. end;
  199. _X ->
  200. ?FAIL
  201. end;
  202. false ->
  203. skipped
  204. end.
  205. -spec yrl_needs_compile(Source::string(), Target::string()) -> boolean().
  206. yrl_needs_compile(Source, Target) ->
  207. filelib:last_modified(Target) < filelib:last_modified(Source).
  208. gather_src([], Srcs) ->
  209. Srcs;
  210. gather_src([Dir|Rest], Srcs) ->
  211. gather_src(Rest, Srcs ++ rebar_utils:find_files(Dir, ".*\\.erl\$")).
  212. -spec src_dirs(SrcDirs::[string()]) -> [string()].
  213. src_dirs([]) ->
  214. ["src"];
  215. src_dirs(SrcDirs) ->
  216. SrcDirs ++ src_dirs([]).
  217. -spec dirs(Dir::string()) -> [string()].
  218. dirs(Dir) ->
  219. [F || F <- filelib:wildcard(filename:join([Dir, "*"])), filelib:is_dir(F)].
  220. -spec delete_dir(Dir::string(), Subdirs::[string()]) -> 'ok' | {'error', atom()}.
  221. delete_dir(Dir, []) ->
  222. file:del_dir(Dir);
  223. delete_dir(Dir, Subdirs) ->
  224. lists:foreach(fun(D) -> delete_dir(D, dirs(D)) end, Subdirs),
  225. file:del_dir(Dir).
  226. -spec compile_priority(File::string()) -> pos_integer().
  227. compile_priority(File) ->
  228. case epp_dodger:parse_file(File) of
  229. {error, _} ->
  230. 10; % couldn't parse the file, default priority
  231. {ok, Trees} ->
  232. F2 = fun({tree,arity_qualifier,_,
  233. {arity_qualifier,{tree,atom,_,behaviour_info},
  234. {tree,integer,_,1}}}, _) ->
  235. 2;
  236. ({tree,arity_qualifier,_,
  237. {arity_qualifier,{tree,atom,_,parse_transform},
  238. {tree,integer,_,2}}}, _) ->
  239. 1;
  240. (_, Acc) ->
  241. Acc
  242. end,
  243. F = fun({tree, attribute, _, {attribute, {tree, atom, _, export},
  244. [{tree, list, _, {list, List, none}}]}}, Acc) ->
  245. lists:foldl(F2, Acc, List);
  246. (_, Acc) ->
  247. Acc
  248. end,
  249. lists:foldl(F, 10, Trees)
  250. end.