Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

208 строки
9.0 KiB

15 лет назад
15 лет назад
15 лет назад
15 лет назад
15 лет назад
15 лет назад
14 лет назад
15 лет назад
14 лет назад
15 лет назад
15 лет назад
15 лет назад
15 лет назад
14 лет назад
15 лет назад
14 лет назад
15 лет назад
15 лет назад
15 лет назад
15 лет назад
14 лет назад
  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) 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. %% @author Dave Smith <dizzyd@dizzyd.com>
  28. %% @doc rebar_dialyzer supports the following commands:
  29. %% <ul>
  30. %% <li>dialyze (essentially "dialyzer ebin" or "dialyzer --src src")</li>
  31. %% <li>build-plt (essentially "dialyzer --build_plt -r &lt;app_dirs&gt;")</li>
  32. %% <li>check-plt (essentially "dialyzer --check_plt")</li>
  33. %% </ul>
  34. %% A single option <code>plt</code> can be presented in the <code>dialyzer_opts</code>
  35. %% options in <code>rebar.config</code>. If it is present, it is used as the PLT for the
  36. %% supported commands. Should it not be present, then the default is <code>$HOME/.dialyzer_plt</code>.
  37. %% @reference <a href="http://user.it.uu.se/~kostis/Papers/bugs05.pdf">Experience from developing the Dialyzer:
  38. %% A static analysis tool detecting defects in Erlang applications</a>
  39. %% @reference <a href="http://user.it.uu.se/~kostis/Papers/contracts.pdf">A Language for Specifying Type
  40. %% Contracts in Erlang and its Interaction with Success Typings</a>
  41. %% @reference <a href="http://user.it.uu.se/~kostis/Papers/wrangler.pdf">Gradual Typing of Erlang
  42. %% Programs: A Wrangler Experience</a>
  43. %% @copyright 2010 Dave Smith
  44. %% -------------------------------------------------------------------
  45. -module(rebar_dialyzer).
  46. -export([dialyze/2,
  47. 'build-plt'/2,
  48. 'check-plt'/2]).
  49. -include("rebar.hrl").
  50. -type(warning() :: {atom(), {string(), integer()}, any()}).
  51. %% ===================================================================
  52. %% Public API
  53. %% ===================================================================
  54. %% @doc Perform static analysis on the contents of the ebin directory.
  55. %% @spec dialyze(Config::#config{}, File::string()) -> ok
  56. -spec dialyze(Config::#config{}, File::string()) -> ok.
  57. dialyze(Config, File) ->
  58. Plt = existing_plt_path(Config, File),
  59. case dialyzer:plt_info(Plt) of
  60. {ok, _} ->
  61. FromSrc = proplists:get_bool(src, rebar_config:get(Config,
  62. dialyzer_opts,
  63. [])),
  64. DialyzerOpts0 = case FromSrc of
  65. true ->
  66. [{files_rec, ["src"]}, {init_plt, Plt},
  67. {from, src_code}];
  68. false ->
  69. [{files_rec, ["ebin"]}, {init_plt, Plt}]
  70. end,
  71. WarnOpts = warnings(Config),
  72. DialyzerOpts = case WarnOpts of
  73. [] -> DialyzerOpts0;
  74. _ -> [{warnings, WarnOpts}|DialyzerOpts0]
  75. end,
  76. ?DEBUG("DialyzerOpts: ~p~n", [DialyzerOpts]),
  77. try dialyzer:run(DialyzerOpts) of
  78. Warnings -> output_warnings(Warnings)
  79. catch
  80. throw:{dialyzer_error, Reason} ->
  81. ?ABORT("~s~n", [Reason])
  82. end;
  83. {error, no_such_file} ->
  84. ?ABORT("The PLT ~s does not exist. Please perform the build-plt "
  85. "command to ~n"
  86. "produce the initial PLT. Be aware that this operation may "
  87. "take several minutes.~n", [Plt]);
  88. {error, read_error} ->
  89. ?ABORT("Unable to read PLT ~n~n", [Plt]);
  90. {error, not_valid} ->
  91. ?ABORT("The PLT ~s is not valid.~n", [Plt])
  92. end,
  93. ok.
  94. %% @doc Build the PLT.
  95. %% @spec 'build-plt'(Config::#config{}, File::string()) -> ok
  96. -spec 'build-plt'(Config::#config{}, File::string()) -> ok.
  97. 'build-plt'(Config, File) ->
  98. Plt = new_plt_path(Config, File),
  99. Apps = rebar_app_utils:app_applications(File),
  100. ?DEBUG("Build PLT ~s including following apps:~n~p~n", [Plt, Apps]),
  101. Warnings = dialyzer:run([{analysis_type, plt_build},
  102. {files_rec, app_dirs(Apps)},
  103. {output_plt, Plt}]),
  104. case Warnings of
  105. [] ->
  106. ?INFO("The built PLT can be found in ~s~n", [Plt]);
  107. _ ->
  108. output_warnings(Warnings)
  109. end,
  110. ok.
  111. %% @doc Check whether the PLT is up-to-date (rebuilding it if not).
  112. %% @spec 'check-plt'(Config::#config{}, File::string()) -> ok
  113. -spec 'check-plt'(Config::#config{}, File::string()) -> ok.
  114. 'check-plt'(Config, File) ->
  115. Plt = existing_plt_path(Config, File),
  116. try dialyzer:run([{analysis_type, plt_check}, {init_plt, Plt}]) of
  117. [] ->
  118. ?CONSOLE("The PLT ~s is up-to-date~n", [Plt]);
  119. _ ->
  120. %% @todo Determine whether this is the correct summary.
  121. ?CONSOLE("The PLT ~s is not up-to-date~n", [Plt])
  122. catch
  123. throw:{dialyzer_error, _Reason} ->
  124. ?CONSOLE("The PLT ~s is not valid.~n", [Plt])
  125. end,
  126. ok.
  127. %% ===================================================================
  128. %% Internal functions
  129. %% ===================================================================
  130. %% @doc Obtain the library paths for the supplied applications.
  131. %% @spec app_dirs(Apps::[atom()]) -> [string()]
  132. -spec app_dirs(Apps::[atom()]) -> [string()].
  133. app_dirs(Apps) ->
  134. [filename:join(Path, "ebin")
  135. || Path <- [code:lib_dir(App) || App <- Apps], erlang:is_list(Path)].
  136. %% @doc Render the warnings on the console.
  137. %% @spec output_warnings(Warnings::[warning()]) -> 'ok'
  138. -spec output_warnings(Warnings::[warning()]) -> 'ok'.
  139. output_warnings(Warnings) ->
  140. lists:foreach(fun(Warning) ->
  141. ?CONSOLE("~s", [dialyzer:format_warning(Warning)])
  142. end, Warnings).
  143. %% @doc If the plt option is present in rebar.config return its value, otherwise
  144. %% return $HOME/.dialyzer_plt.
  145. %% @spec new_plt_path(Config::#config{}, File::string()) -> string()
  146. -spec new_plt_path(Config::#config{}, File::string()) -> string().
  147. new_plt_path(Config, File) ->
  148. AppName = rebar_app_utils:app_name(File),
  149. DialyzerOpts = rebar_config:get(Config, dialyzer_opts, []),
  150. case proplists:get_value(plt, DialyzerOpts) of
  151. undefined ->
  152. filename:join(os:getenv("HOME"),
  153. "." ++ atom_to_list(AppName) ++ "_dialyzer_plt");
  154. Plt ->
  155. Plt
  156. end.
  157. %% @doc If the plt option is present in rebar.config and the file exists
  158. %% return its value or if ~/.AppName_dialyzer_plt exists return that.
  159. %% Otherwise return ~/.dialyzer_plt if it exists or abort.
  160. %% @spec existing_plt_path(Config::#config{}, File::string()) -> string()
  161. -spec existing_plt_path(Config::#config{}, File::string()) -> string().
  162. existing_plt_path(Config, File) ->
  163. AppName = rebar_app_utils:app_name(File),
  164. DialyzerOpts = rebar_config:get(Config, dialyzer_opts, []),
  165. Home = os:getenv("HOME"),
  166. case proplists:get_value(plt, DialyzerOpts) of
  167. undefined ->
  168. AppPlt = filename:join(Home, "." ++ atom_to_list(AppName)
  169. ++ "_dialyzer_plt"),
  170. case filelib:is_regular(AppPlt) of
  171. true ->
  172. AppPlt;
  173. false ->
  174. HomePlt = filename:join(Home, ".dialyzer_plt"),
  175. case filelib:is_regular(HomePlt) of
  176. true ->
  177. HomePlt;
  178. false ->
  179. ?ABORT("No PLT found~n", [])
  180. end
  181. end;
  182. [$~|[$/|Plt]] ->
  183. filename:join(Home,Plt);
  184. Plt ->
  185. Plt
  186. end.
  187. %% @doc If the warnings option is present in rebar.config return its value,
  188. %% otherwise return [].
  189. %% @spec warnings(Config::#config{}) -> list()
  190. -spec warnings(Config::#config{}) -> list().
  191. warnings(Config) ->
  192. DialyzerOpts = rebar_config:get(Config, dialyzer_opts, []),
  193. proplists:get_value(warnings, DialyzerOpts, []).