|
| 1 | +%%% Run with 'escript app_deps.erl' |
| 2 | +%%% Change the path in filelib:wildcard/1 as required to capture |
| 3 | +%%% all your dependencies. |
| 4 | +%%% |
| 5 | +%%% Rectangular nodes will represent library apps (no processes |
| 6 | +%%% involved) and the circular nodes will represent regular apps. |
| 7 | +%%% An arrow going from 'A -> B' means 'A depends on B'. |
| 8 | +%%% |
| 9 | +%%% This script depends on graphviz being present on the system. |
| 10 | +-module(app_deps). |
| 11 | +-export([main/1]). |
| 12 | + |
| 13 | +main(_) -> |
| 14 | + AppFiles = filelib:wildcard("deps/*/ebin/*.app") |
| 15 | + ++ |
| 16 | + filelib:wildcard("ebin/*.app"), |
| 17 | + to_graphviz(read_deps(AppFiles)). |
| 18 | + |
| 19 | +read_deps(AppFiles) -> |
| 20 | + [{App, |
| 21 | + proplists:get_value(applications, Props, []), |
| 22 | + apptype(Props)} |
| 23 | + || {ok, [{_,App,Props}]} <- |
| 24 | + [file:consult(AppFile) || AppFile <- AppFiles]]. |
| 25 | + |
| 26 | +apptype(Props) -> |
| 27 | + case proplists:get_value(mod, Props) of |
| 28 | + undefined -> library; |
| 29 | + _ -> regular |
| 30 | + end. |
| 31 | + |
| 32 | +to_graphviz(Deps) -> |
| 33 | + AllApps = lists:usort(lists:flatten( |
| 34 | + [[{App,Type},DepList] || {App,DepList,Type} <- Deps] |
| 35 | + )), |
| 36 | + Bytes = ["digraph G { ", |
| 37 | + "K=0.25; ratio=0.75; overlap=\"9:prism\"; ", |
| 38 | + [io_lib:format("~p [shape=box] ", [App]) |
| 39 | + || App <- libapps(AllApps -- [kernel,stdlib])], |
| 40 | + [[io_lib:format("~p->~p ", [App,Dep]) |
| 41 | + || Dep <- DepList -- [kernel, stdlib]] |
| 42 | + || {App, DepList, _} <- Deps], |
| 43 | + "}"], |
| 44 | + file:write_file("app-deps.dot", Bytes), |
| 45 | + os:cmd("dot app-deps.dot -Tpng -o app-deps.png"). |
| 46 | + |
| 47 | +libapps([]) -> []; |
| 48 | +libapps([{App,library}|Apps]) -> [App|libapps(Apps)]; |
| 49 | +libapps([{_,_}|Apps]) -> libapps(Apps); |
| 50 | +libapps([App|Apps]) -> |
| 51 | + Dir = case code:lib_dir(App) of |
| 52 | + {error, _} -> ""; % not an OTP app |
| 53 | + DirPath -> DirPath |
| 54 | + end, |
| 55 | + Path = filename:join([Dir, "ebin", atom_to_list(App)++".app"]), |
| 56 | + case lists:prefix(code:lib_dir(), Path) of |
| 57 | + false -> |
| 58 | + [App|libapps(Apps)]; % not OTP app, we don't care |
| 59 | + true -> % deps of OTP deps: we don't care either. |
| 60 | + {ok, [{_,App,Props}]} = file:consult(Path), |
| 61 | + case apptype(Props) of |
| 62 | + library -> [App | libapps(Apps)]; |
| 63 | + regular -> libapps(Apps) |
| 64 | + end |
| 65 | + end. |
0 commit comments