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.

580 regels
22 KiB

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