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

620 строки
23 KiB

10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
6 лет назад
10 лет назад
9 лет назад
10 лет назад
9 лет назад
9 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
10 лет назад
6 лет назад
  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. -module(rebar_prv_dialyzer).
  4. -behaviour(provider).
  5. -export([init/1,
  6. do/1,
  7. format_error/1]).
  8. -include("rebar.hrl").
  9. -include_lib("providers/include/providers.hrl").
  10. -define(PROVIDER, dialyzer).
  11. -define(DEPS, [compile]).
  12. -define(PLT_PREFIX, "rebar3").
  13. %% ===================================================================
  14. %% Public API
  15. %% ===================================================================
  16. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  17. init(State) ->
  18. Opts = [{update_plt, $u, "update-plt", boolean, "Enable updating the PLT. Default: true"},
  19. {succ_typings, $s, "succ-typings", boolean, "Enable success typing analysis. Default: true"},
  20. {base_plt_location, undefined, "base-plt-location", string, "The location of base PLT file, defaults to $HOME/.cache/rebar3"},
  21. {plt_location, undefined, "plt-location", string, "The location of the PLT file, defaults to the profile's base directory"},
  22. {plt_prefix, undefined, "plt-prefix", string, "The prefix to the PLT file, defaults to \"rebar3\"" },
  23. {base_plt_prefix, undefined, "base-plt-prefix", string, "The prefix to the base PLT file, defaults to \"rebar3\"" },
  24. {statistics, undefined, "statistics", boolean, "Print information about the progress of execution. Default: false" }],
  25. State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER},
  26. {module, ?MODULE},
  27. {bare, true},
  28. {deps, ?DEPS},
  29. {example, "rebar3 dialyzer"},
  30. {short_desc, short_desc()},
  31. {desc, desc()},
  32. {opts, Opts}])),
  33. {ok, State1}.
  34. desc() ->
  35. short_desc() ++ "\n"
  36. "\n"
  37. "This command will build, and keep up-to-date, a suitable PLT and will use "
  38. "it to carry out success typing analysis on the current project.\n"
  39. "\n"
  40. "The following (optional) configurations can be added to a `proplist` of "
  41. "options `dialyzer` in rebar.config:\n"
  42. "`warnings` - a list of dialyzer warnings\n"
  43. "`get_warnings` - display warnings when altering a PLT file (boolean)\n"
  44. "`plt_apps` - the strategy for determining the applications which included "
  45. "in the PLT file, `top_level_deps` to include just the direct dependencies "
  46. "or `all_deps` to include all nested dependencies*\n"
  47. "`plt_extra_apps` - a list of extra applications to include in the PLT "
  48. "file\n"
  49. "`plt_extra_mods` - a list of extra modules to includes in the PLT file\n"
  50. "`plt_location` - the location of the PLT file, `local` to store in the "
  51. "profile's base directory (default) or a custom directory.\n"
  52. "`plt_prefix` - the prefix to the PLT file, defaults to \"rebar3\"**\n"
  53. "`base_plt_apps` - a list of applications to include in the base "
  54. "PLT file***\n"
  55. "`base_plt_mods` - a list of modules to include in the base "
  56. "PLT file***\n"
  57. "`base_plt_location` - the location of base PLT file, `global` to store in "
  58. "$HOME/.cache/rebar3 (default) or a custom directory***\n"
  59. "`base_plt_prefix` - the prefix to the base PLT file, defaults to "
  60. "\"rebar3\"** ***\n"
  61. "`exclude_apps` - a list of applications to exclude from PLT files and "
  62. "success typing analysis, `plt_extra_mods` and `base_plt_mods` can add "
  63. "modules from excluded applications\n"
  64. "`exclude_mods` - a list of modules to exclude from PLT files and "
  65. "success typing analysis\n"
  66. "\n"
  67. "For example, to warn on unmatched returns: \n"
  68. "{dialyzer, [{warnings, [unmatched_returns]}]}.\n"
  69. "\n"
  70. "*The direct dependent applications are listed in `applications` and "
  71. "`included_applications` of their .app files.\n"
  72. "**PLT files are named \"<prefix>_<otp_release>_plt\".\n"
  73. "***The base PLT is a PLT containing the core applications often required "
  74. "for a project's PLT. One base PLT is created per OTP version and "
  75. "stored in `base_plt_location`. A base PLT is used to build project PLTs."
  76. "\n".
  77. short_desc() ->
  78. "Run the Dialyzer analyzer on the project.".
  79. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  80. do(State) ->
  81. maybe_fix_env(),
  82. ?INFO("Dialyzer starting, this may take a while...", []),
  83. rebar_paths:unset_paths([plugins], State), % no plugins in analysis
  84. rebar_paths:set_paths([deps], State),
  85. {Args, _} = rebar_state:command_parsed_args(State),
  86. Plt = get_plt(Args, State),
  87. try
  88. do(Args, State, Plt)
  89. catch
  90. throw:{dialyzer_error, Error} ->
  91. ?PRV_ERROR({error_processing_apps, Error});
  92. throw:{dialyzer_warnings, Warnings} ->
  93. ?PRV_ERROR({dialyzer_warnings, Warnings});
  94. throw:{unknown_application, _} = Error ->
  95. ?PRV_ERROR(Error);
  96. throw:{unknown_module, _} = Error ->
  97. ?PRV_ERROR(Error);
  98. throw:{duplicate_module, _, _, _} = Error ->
  99. ?PRV_ERROR(Error);
  100. throw:{output_file_error, _, _} = Error ->
  101. ?PRV_ERROR(Error)
  102. after
  103. rebar_paths:set_paths([plugins,deps], State)
  104. end.
  105. %% This is used to workaround dialyzer quirk discussed here
  106. %% https://github.com/erlang/rebar3/pull/489#issuecomment-107953541
  107. %% Dialyzer gets default plt location wrong way by peeking HOME environment
  108. %% variable which usually is not defined on Windows.
  109. maybe_fix_env() ->
  110. os:putenv("DIALYZER_PLT", filename:join(rebar_dir:home_dir(), ".dialyzer_plt")).
  111. -spec format_error(any()) -> iolist().
  112. format_error({error_processing_apps, Error}) ->
  113. io_lib:format("Error in dialyzing apps: ~ts", [Error]);
  114. format_error({dialyzer_warnings, Warnings}) ->
  115. io_lib:format("Warnings occurred running dialyzer: ~b", [Warnings]);
  116. format_error({unknown_application, App}) ->
  117. io_lib:format("Could not find application: ~ts", [App]);
  118. format_error({unknown_module, Mod}) ->
  119. io_lib:format("Could not find module: ~ts", [Mod]);
  120. format_error({duplicate_module, Mod, File1, File2}) ->
  121. io_lib:format("Duplicates of module ~ts: ~ts ~ts", [Mod, File1, File2]);
  122. format_error({output_file_error, File, Error}) ->
  123. Error1 = file:format_error(Error),
  124. io_lib:format("Failed to write to ~ts: ~ts", [File, Error1]);
  125. format_error(Reason) ->
  126. io_lib:format("~p", [Reason]).
  127. %% Internal functions
  128. get_plt(Args, State) ->
  129. Prefix = proplists:get_value(plt_prefix, Args, get_config(State, plt_prefix, ?PLT_PREFIX)),
  130. Name = plt_name(Prefix),
  131. case proplists:get_value(plt_location, Args, get_config(State, plt_location, local)) of
  132. local ->
  133. BaseDir = rebar_dir:base_dir(State),
  134. filename:join(BaseDir, Name);
  135. Dir ->
  136. filename:join(Dir, Name)
  137. end.
  138. plt_name(Prefix) ->
  139. Prefix ++ "_" ++ rebar_utils:otp_release() ++ "_plt".
  140. do(Args, State, Plt) ->
  141. Output = get_output_file(State),
  142. case debug_info(State) of
  143. true ->
  144. ok;
  145. false ->
  146. ?WARN("Add debug_info to compiler options (erl_opts) "
  147. "if Dialyzer fails to load Core Erlang.", [])
  148. end,
  149. {PltWarnings, State1} = update_proj_plt(Args, State, Plt, Output),
  150. {Warnings, State2} = succ_typings(Args, State1, Plt, Output),
  151. case PltWarnings + Warnings of
  152. 0 ->
  153. {ok, State2};
  154. TotalWarnings ->
  155. ?INFO("Warnings written to ~ts", [Output]),
  156. throw({dialyzer_warnings, TotalWarnings})
  157. end.
  158. get_output_file(State) ->
  159. BaseDir = rebar_dir:base_dir(State),
  160. Output = filename:join(BaseDir, default_output_file()),
  161. case file:open(Output, [write]) of
  162. {ok, File} ->
  163. ok = file:close(File),
  164. Output;
  165. {error, Reason} ->
  166. throw({output_file_error, Output, Reason})
  167. end.
  168. default_output_file() ->
  169. rebar_utils:otp_release() ++ ".dialyzer_warnings".
  170. update_proj_plt(Args, State, Plt, Output) ->
  171. case proplists:get_value(update_plt, Args) of
  172. false ->
  173. {0, State};
  174. _ ->
  175. do_update_proj_plt(Args, State, Plt, Output)
  176. end.
  177. do_update_proj_plt(Args, State, Plt, Output) ->
  178. ?INFO("Updating plt...", []),
  179. Files = proj_plt_files(State),
  180. case read_plt(State, Plt) of
  181. {ok, OldFiles} ->
  182. check_plt(State, Plt, Output, OldFiles, Files);
  183. error ->
  184. build_proj_plt(Args, State, Plt, Output, Files)
  185. end.
  186. proj_plt_files(State) ->
  187. BasePltApps = base_plt_apps(State),
  188. PltApps = get_config(State, plt_extra_apps, []) ++ BasePltApps,
  189. BasePltMods = get_config(State, base_plt_mods, []),
  190. PltMods = get_config(State, plt_extra_mods, []) ++ BasePltMods,
  191. Apps = proj_apps(State),
  192. DepApps = proj_deps(State),
  193. get_files(State, DepApps ++ PltApps, Apps -- PltApps, PltMods, [], []).
  194. proj_apps(State) ->
  195. [ec_cnv:to_atom(rebar_app_info:name(App)) ||
  196. App <- rebar_state:project_apps(State)].
  197. proj_deps(State) ->
  198. Apps = rebar_state:project_apps(State),
  199. DepApps = lists:flatmap(fun rebar_app_info:applications/1, Apps),
  200. case get_config(State, plt_apps, top_level_deps) of
  201. top_level_deps -> DepApps;
  202. all_deps -> collect_nested_dependent_apps(DepApps)
  203. end.
  204. get_files(State, Apps, SkipApps, Mods, SkipMods, ExtraDirs) ->
  205. ?INFO("Resolving files...", []),
  206. ExcludeApps = get_config(State, exclude_apps, []),
  207. Files0 = apps_files(Apps, ExcludeApps ++ SkipApps, ExtraDirs, dict:new()),
  208. BaseDir = filename:join(rebar_dir:base_dir(State), "extras"),
  209. Files1 = extras_files(BaseDir, ExtraDirs, Files0),
  210. ExcludeMods = get_config(State, exclude_mods, []),
  211. Files2 = mods_files(Mods, ExcludeMods ++ SkipMods, Files1),
  212. dict:fold(fun(_, File, Acc) -> [File | Acc] end, [], Files2).
  213. apps_files([], _, _ExtraDirs, Files) ->
  214. Files;
  215. apps_files([AppName | DepApps], SkipApps, ExtraDirs, Files) ->
  216. case lists:member(AppName, SkipApps) of
  217. true ->
  218. apps_files(DepApps, SkipApps, ExtraDirs, Files);
  219. false ->
  220. AppFiles = app_files(AppName, ExtraDirs),
  221. ?DEBUG("~ts modules: ~p", [AppName, dict:fetch_keys(AppFiles)]),
  222. Files2 = merge_files(Files, AppFiles),
  223. apps_files(DepApps, [AppName | SkipApps], ExtraDirs, Files2)
  224. end.
  225. app_files(AppName, ExtraDirs) ->
  226. case app_ebin(AppName) of
  227. {ok, EbinDir} ->
  228. merge_files(ebin_files(EbinDir), extra_files(AppName, ExtraDirs));
  229. {error, bad_name} ->
  230. throw({unknown_application, AppName})
  231. end.
  232. app_ebin(AppName) ->
  233. case code:lib_dir(AppName, ebin) of
  234. {error, bad_name} = Error ->
  235. Error;
  236. EbinDir ->
  237. check_ebin(EbinDir)
  238. end.
  239. check_ebin(EbinDir) ->
  240. case filelib:is_dir(EbinDir) of
  241. true ->
  242. {ok, EbinDir};
  243. false ->
  244. {error, bad_name}
  245. end.
  246. ebin_files(EbinDir) ->
  247. Ext = code:objfile_extension(),
  248. Wildcard = "*" ++ Ext,
  249. Files = filelib:wildcard(Wildcard, EbinDir),
  250. Store = fun(File, Mods) ->
  251. Mod = list_to_atom(filename:basename(File, Ext)),
  252. Absname = filename:join(EbinDir, File),
  253. dict:store(Mod, Absname, Mods)
  254. end,
  255. lists:foldl(Store, dict:new(), Files).
  256. extras_files(_BaseDir, [], Acc) ->
  257. Acc;
  258. extras_files(BaseDir, [ExtraDir | Rest], Acc) ->
  259. Files = ebin_files(filename:join(BaseDir, ExtraDir)),
  260. extras_files(BaseDir, Rest, merge_files(Acc, Files)).
  261. extra_files(AppName, ExtraDirs) ->
  262. lists:foldl(
  263. fun(ExtraDir, Files) ->
  264. merge_files(Files, ebin_files(filename:join(code:lib_dir(AppName), ExtraDir)))
  265. end,
  266. dict:new(),
  267. ExtraDirs
  268. ).
  269. merge_files(Files1, Files2) ->
  270. Duplicate = fun(Mod, File1, File2) ->
  271. throw({duplicate_module, Mod, File1, File2})
  272. end,
  273. dict:merge(Duplicate, Files1, Files2).
  274. mods_files(Mods, SkipMods, Files) ->
  275. Keep = fun(File) -> File end,
  276. Ensure = fun(Mod, Acc) ->
  277. case lists:member(Mod, SkipMods) of
  278. true ->
  279. Acc;
  280. false ->
  281. dict:update(Mod, Keep, mod_file(Mod), Acc)
  282. end
  283. end,
  284. Files2 = lists:foldl(Ensure, Files, Mods),
  285. lists:foldl(fun dict:erase/2, Files2, SkipMods).
  286. mod_file(Mod) ->
  287. File = atom_to_list(Mod) ++ code:objfile_extension(),
  288. case code:where_is_file(File) of
  289. non_existing -> throw({unknown_module, Mod});
  290. Absname -> Absname
  291. end.
  292. read_plt(_State, Plt) ->
  293. Vsn = dialyzer_version(),
  294. case plt_files(Plt) of
  295. {ok, Files} when Vsn < {2, 9, 0} ->
  296. % Before dialyzer-2.9 (OTP 18.3) removing a beam file from the PLT
  297. % that no longer exists would crash. Therefore force a rebuild of
  298. % PLT if any files no longer exist.
  299. read_plt_files(Plt, Files);
  300. {ok, _} = Result when Vsn >= {2, 9, 0} ->
  301. Result;
  302. {error, no_such_file} ->
  303. error;
  304. {error, not_valid} ->
  305. error;
  306. {error, read_error} ->
  307. Error = io_lib:format("Could not read the PLT file ~p", [Plt]),
  308. throw({dialyzer_error, Error})
  309. end.
  310. plt_files(Plt) ->
  311. case dialyzer:plt_info(Plt) of
  312. {ok, Info} ->
  313. {ok, proplists:get_value(files, Info, [])};
  314. {error, _} = Error ->
  315. Error
  316. end.
  317. %% If any file no longer exists dialyzer will fail when updating the PLT.
  318. read_plt_files(Plt, Files) ->
  319. case [File || File <- Files, not filelib:is_file(File)] of
  320. [] ->
  321. {ok, Files};
  322. Missing ->
  323. ?INFO("Could not find ~p files in ~p...", [length(Missing), Plt]),
  324. ?DEBUG("Could not find files: ~p", [Missing]),
  325. error
  326. end.
  327. check_plt(State, Plt, Output, OldList, FilesList) ->
  328. Old = sets:from_list(OldList),
  329. Files = sets:from_list(FilesList),
  330. Remove = sets:to_list(sets:subtract(Old, Files)),
  331. {RemWarnings, State1} = remove_plt(State, Plt, Output, Remove),
  332. Check = sets:to_list(sets:intersection(Files, Old)),
  333. {CheckWarnings, State2} = check_plt(State1, Plt, Output, Check),
  334. Add = sets:to_list(sets:subtract(Files, Old)),
  335. {AddWarnings, State3} = add_plt(State2, Plt, Output, Add),
  336. {RemWarnings + CheckWarnings + AddWarnings, State3}.
  337. remove_plt(State, _Plt, _Output, []) ->
  338. {0, State};
  339. remove_plt(State, Plt, Output, Files) ->
  340. ?INFO("Removing ~b files from ~p...", [length(Files), Plt]),
  341. run_plt(State, Plt, Output, plt_remove, Files).
  342. check_plt(State, _Plt, _Output, []) ->
  343. {0, State};
  344. check_plt(State, Plt, Output, Files) ->
  345. ?INFO("Checking ~b files in ~p...", [length(Files), Plt]),
  346. run_plt(State, Plt, Output, plt_check, Files).
  347. add_plt(State, _Plt, _Output, []) ->
  348. {0, State};
  349. add_plt(State, Plt, Output, Files) ->
  350. ?INFO("Adding ~b files to ~p...", [length(Files), Plt]),
  351. run_plt(State, Plt, Output, plt_add, Files).
  352. run_plt(State, Plt, Output, Analysis, Files) ->
  353. GetWarnings = get_config(State, get_warnings, false),
  354. Opts = [{analysis_type, Analysis},
  355. {get_warnings, GetWarnings},
  356. {init_plt, Plt},
  357. {output_plt, Plt},
  358. {from, byte_code},
  359. {files, Files}],
  360. run_dialyzer(State, Opts, Output).
  361. build_proj_plt(Args, State, Plt, Output, Files) ->
  362. BasePlt = get_base_plt(Args, State),
  363. ?INFO("Updating base plt...", []),
  364. BaseFiles = base_plt_files(State),
  365. {BaseWarnings, State1} = update_base_plt(State, BasePlt, Output, BaseFiles),
  366. ?INFO("Copying ~p to ~p...", [BasePlt, Plt]),
  367. _ = filelib:ensure_dir(Plt),
  368. case file:copy(BasePlt, Plt) of
  369. {ok, _} ->
  370. {CheckWarnings, State2} = check_plt(State1, Plt, Output, BaseFiles,
  371. Files),
  372. {BaseWarnings + CheckWarnings, State2};
  373. {error, Reason} ->
  374. Error = io_lib:format("Could not copy PLT from ~p to ~p: ~p",
  375. [BasePlt, Plt, file:format_error(Reason)]),
  376. throw({dialyzer_error, Error})
  377. end.
  378. get_base_plt(Args, State) ->
  379. Prefix = proplists:get_value(base_plt_prefix, Args, get_config(State, base_plt_prefix, ?PLT_PREFIX)),
  380. Name = plt_name(Prefix),
  381. case proplists:get_value(base_plt_location, Args, get_config(State, base_plt_location, global)) of
  382. global ->
  383. GlobalCacheDir = rebar_dir:global_cache_dir(rebar_state:opts(State)),
  384. filename:join(GlobalCacheDir, Name);
  385. Dir ->
  386. filename:join(Dir, Name)
  387. end.
  388. base_plt_files(State) ->
  389. BasePltApps = base_plt_apps(State),
  390. BasePltMods = get_config(State, base_plt_mods, []),
  391. get_files(State, BasePltApps, [], BasePltMods, [], []).
  392. base_plt_apps(State) ->
  393. get_config(State, base_plt_apps, [erts, crypto, kernel, stdlib]).
  394. update_base_plt(State, BasePlt, Output, BaseFiles) ->
  395. case read_plt(State, BasePlt) of
  396. {ok, OldBaseFiles} ->
  397. check_plt(State, BasePlt, Output, OldBaseFiles, BaseFiles);
  398. error ->
  399. _ = filelib:ensure_dir(BasePlt),
  400. build_plt(State, BasePlt, Output, BaseFiles)
  401. end.
  402. build_plt(State, Plt, _, []) ->
  403. ?INFO("Building with no files in ~p...", [Plt]),
  404. Opts = [{get_warnings, false},
  405. {output_plt, Plt},
  406. {apps, [erts]}],
  407. % Create a PLT with erts files and then remove erts files to be left with an
  408. % empty PLT. Dialyzer will crash when trying to build a PLT with an empty
  409. % file list.
  410. _ = dialyzer:run([{analysis_type, plt_build} | Opts]),
  411. _ = dialyzer:run([{analysis_type, plt_remove}, {init_plt, Plt} | Opts]),
  412. {0, State};
  413. build_plt(State, Plt, Output, Files) ->
  414. ?INFO("Building with ~b files in ~p...", [length(Files), Plt]),
  415. GetWarnings = get_config(State, get_warnings, false),
  416. Opts = [{analysis_type, plt_build},
  417. {get_warnings, GetWarnings},
  418. {output_plt, Plt},
  419. {files, Files}],
  420. run_dialyzer(State, Opts, Output).
  421. succ_typings(Args, State, Plt, Output) ->
  422. case proplists:get_value(succ_typings, Args) of
  423. false ->
  424. {0, State};
  425. _ ->
  426. ?INFO("Doing success typing analysis...", []),
  427. Files = proj_files(State),
  428. succ_typings_(State, Plt, Output, Files)
  429. end.
  430. succ_typings_(State, Plt, _, []) ->
  431. ?INFO("Analyzing no files with ~p...", [Plt]),
  432. {0, State};
  433. succ_typings_(State, Plt, Output, Files) ->
  434. ?INFO("Analyzing ~b files with ~p...", [length(Files), Plt]),
  435. Opts = [{analysis_type, succ_typings},
  436. {get_warnings, true},
  437. {from, byte_code},
  438. {files, Files},
  439. {init_plt, Plt}],
  440. run_dialyzer(State, Opts, Output).
  441. proj_files(State) ->
  442. Apps = proj_apps(State),
  443. BasePltApps = get_config(State, base_plt_apps, []),
  444. PltApps = get_config(State, plt_extra_apps, []) ++ BasePltApps,
  445. BasePltMods = get_config(State, base_plt_mods, []),
  446. PltMods = get_config(State, plt_extra_mods, []) ++ BasePltMods,
  447. ExtraDirs = rebar_dir:extra_src_dirs(rebar_state:opts(State)),
  448. get_files(State, Apps, PltApps, [], PltMods, ExtraDirs).
  449. run_dialyzer(State, Opts, Output) ->
  450. {Args, _} = rebar_state:command_parsed_args(State),
  451. %% dialyzer uses command-line option `--statistics` for enabling
  452. %% additional info about progress of execution, but internally
  453. %% this option has name `timing`.
  454. %% NOTE: Option `timing` accept boolean() or 'debug', but here we support
  455. %% only boolean().
  456. Timing = proplists:get_bool(statistics, Args),
  457. %% dialyzer may return callgraph warnings when get_warnings is false
  458. case proplists:get_bool(get_warnings, Opts) of
  459. true ->
  460. WarningsList = get_config(State, warnings, []),
  461. Opts2 = [{warnings, legacy_warnings(WarningsList)},
  462. {check_plt, false},
  463. {timing, Timing} |
  464. Opts],
  465. ?DEBUG("Running dialyzer with options: ~p~n", [Opts2]),
  466. Warnings = format_warnings(rebar_state:opts(State),
  467. Output, dialyzer:run(Opts2)),
  468. {Warnings, State};
  469. false ->
  470. Opts2 = [{warnings, no_warnings()},
  471. {check_plt, false} |
  472. Opts],
  473. ?DEBUG("Running dialyzer with options: ~p~n", [Opts2]),
  474. dialyzer:run(Opts2),
  475. {0, State}
  476. end.
  477. legacy_warnings(Warnings) ->
  478. case dialyzer_version() of
  479. TupleVsn when TupleVsn < {2, 8, 0} ->
  480. [Warning || Warning <- Warnings, Warning =/= unknown];
  481. _ ->
  482. Warnings
  483. end.
  484. format_warnings(Opts, Output, Warnings) ->
  485. Warnings1 = rebar_dialyzer_format:format_warnings(Opts, Warnings),
  486. console_warnings(Warnings1),
  487. file_warnings(Output, Warnings),
  488. length(Warnings).
  489. console_warnings(Warnings) ->
  490. _ = [?CONSOLE("~ts", [Warning]) || Warning <- Warnings],
  491. ok.
  492. file_warnings(_, []) ->
  493. ok;
  494. file_warnings(Output, Warnings) ->
  495. Warnings1 = [[dialyzer:format_warning(Warning, fullpath), $\n] || Warning <- Warnings],
  496. case file:write_file(Output, Warnings1, [append]) of
  497. ok ->
  498. ok;
  499. {error, Reason} ->
  500. throw({output_file_error, Output, Reason})
  501. end.
  502. no_warnings() ->
  503. [no_return,
  504. no_unused,
  505. no_improper_lists,
  506. no_fun_app,
  507. no_match,
  508. no_opaque,
  509. no_fail_call,
  510. no_contracts,
  511. no_behaviours,
  512. no_undefined_callbacks].
  513. get_config(State, Key, Default) ->
  514. Config = rebar_state:get(State, dialyzer, []),
  515. proplists:get_value(Key, Config, Default).
  516. debug_info(State) ->
  517. Config = rebar_state:get(State, erl_opts, []),
  518. proplists:get_value(debug_info, Config, false) =/= false orelse
  519. proplists:get_value(debug_info_key, Config, false) =/= false orelse
  520. proplists:get_value(encrypt_debug_info, Config, false) =/= false.
  521. -spec collect_nested_dependent_apps([atom()]) -> [atom()].
  522. collect_nested_dependent_apps(RootApps) ->
  523. Deps = lists:foldl(fun collect_nested_dependent_apps/2, sets:new(), RootApps),
  524. sets:to_list(Deps).
  525. -spec collect_nested_dependent_apps(atom(), rebar_set()) -> rebar_set().
  526. collect_nested_dependent_apps(App, Seen) ->
  527. case sets:is_element(App, Seen) of
  528. true ->
  529. Seen;
  530. false ->
  531. Seen1 = sets:add_element(App, Seen),
  532. case code:lib_dir(App) of
  533. {error, _} ->
  534. throw({unknown_application, App});
  535. AppDir ->
  536. case rebar_app_discover:find_app(AppDir, all) of
  537. false ->
  538. throw({unknown_application, App});
  539. {true, AppInfo} ->
  540. lists:foldl(fun collect_nested_dependent_apps/2,
  541. Seen1,
  542. rebar_app_info:applications(AppInfo))
  543. end
  544. end
  545. end.
  546. dialyzer_version() ->
  547. _ = application:load(dialyzer),
  548. {ok, Vsn} = application:get_key(dialyzer, vsn),
  549. case rebar_string:lexemes(Vsn, ".") of
  550. [Major, Minor] ->
  551. version_tuple(Major, Minor, "0");
  552. [Major, Minor, Patch | _] ->
  553. version_tuple(Major, Minor, Patch)
  554. end.
  555. version_tuple(Major, Minor, Patch) ->
  556. {list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)}.