#!/usr/bin/env escript %% -*- erlang -*- %%% Run with 'escript app_deps.erl' %%% Change the path in filelib:wildcard/1 as required to capture %%% all your dependencies. %%% %%% Rectangular nodes will represent library apps (no processes %%% involved) and the circular nodes will represent regular apps. %%% An arrow going from 'A -> B' means 'A depends on B'. %%% %%% This script depends on graphviz being present on the system. -module(app_deps). -export([main/1]). main(_) -> AppFiles = filelib:wildcard("deps/*/ebin/*.app") ++ filelib:wildcard("apps/*/ebin/*.app") ++ filelib:wildcard("ebin/*.app") ++ filelib:wildcard("_build/default/lib/*/ebin/*.app"), to_graphviz(read_deps(AppFiles)). read_deps(AppFiles) -> [{App, proplists:get_value(applications, Props, []), apptype(Props)} || {ok, [{_,App,Props}]} <- [file:consult(AppFile) || AppFile <- AppFiles]]. apptype(Props) -> case proplists:get_value(mod, Props) of undefined -> library; _ -> regular end. to_graphviz(Deps) -> AllApps = lists:usort(lists:flatten( [[{App,Type},DepList] || {App,DepList,Type} <- Deps] )), Bytes = ["digraph G { ", "K=0.25; ratio=0.75; overlap=\"9:prism\"; ", [io_lib:format("~p [shape=box] ", [App]) || App <- libapps(AllApps -- [kernel,stdlib])], [[io_lib:format("~p->~p ", [App,Dep]) || Dep <- DepList -- [kernel, stdlib]] || {App, DepList, _} <- Deps], "}"], file:write_file("app-deps.dot", Bytes), os:cmd("dot app-deps.dot -Tpng -o app-deps.png"). libapps([]) -> []; libapps([{App,library}|Apps]) -> [App|libapps(Apps)]; libapps([{_,_}|Apps]) -> libapps(Apps); libapps([App|Apps]) -> Dir = case code:lib_dir(App) of {error, _} -> ""; % not an OTP app DirPath -> DirPath end, Path = filename:join([Dir, "ebin", atom_to_list(App)++".app"]), case lists:prefix(code:lib_dir(), Path) of false -> [App|libapps(Apps)]; % not OTP app, we don't care true -> % deps of OTP deps: we don't care either. {ok, [{_,App,Props}]} = file:consult(Path), case apptype(Props) of library -> [App | libapps(Apps)]; regular -> libapps(Apps) end end.