forked from 2600hz/kazoo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
list-ext-deps.escript
executable file
·150 lines (132 loc) · 5.16 KB
/
list-ext-deps.escript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/env escript
%%! -noinput
%%%=============================================================================
%%% Analyze every .beam file in the given paths, find out which external calls
%%% each make, and list those calls that are to the standard Erlang apps (such
%%% as stdlib).
%%%
%%% When multiple paths are given, cumulative results are displayed (i.e. the
%%% union of the sets of modules used by the beam files in question).
%%%
%%% The beam files must be compiled with debug_info otherwise those files will
%%% be skipped with an error message.
%%%=============================================================================
-mode(compile).
main([_|_]=DirList) ->
GoodDirs = check_dirs_exist(DirList),
list_deps(GoodDirs);
main(_) ->
usage().
list_deps(Dirs) ->
Xref = 'list-ext-deps',
{ok, _Pid} = xref:start(Xref),
AppCalls = lists:foldl(fun(Dir, Acc) ->
analyze_beams(Xref, Dir) ++ Acc
end, [], Dirs),
xref:stop(Xref),
ErlApps = all_erlang_apps(),
Deps = find_erlang_deps(lists:usort(AppCalls), ErlApps),
lists:foreach(fun(Dep) ->
io:format("~p\n", [Dep])
end, Deps).
%% Compare the list of module calls made by the app
%% with the modules provided by erlang, and return
%% a list of Erlang applications used by the AppCalls.
-type app_calls() :: [module()].
-type erl_app() :: {app_name(), [module()]}.
-type erl_apps() :: [erl_app()].
-type app_name() :: atom().
-spec find_erlang_deps(app_calls(), erl_apps()) -> [app_name()].
find_erlang_deps(AppCalls, ErlApps) ->
Index = make_inverted_index(ErlApps),
lists:usort([Mod
|| AppCall <- AppCalls,
case maps:get(AppCall, Index, false) of
false -> Mod = false;
Mod -> true
end]).
%% Take a proplist of keys and associated arrays and invert
%% it to a map of array elements to keys.
%% In other words, [{key1, [el1, el2, el3]}] ->
%% %{el1: key1, el2: key1, el3: key1}
make_inverted_index(ErlApps) ->
lists:foldl(fun({App, Mods}, Map) ->
lists:foldl(fun(Mod, Acc) ->
maps:put(Mod, App, Acc)
end, Map, Mods)
end, #{}, ErlApps).
-spec analyze_beams(atom(), string()) -> app_calls().
analyze_beams(Xref, BaseDir) ->
Beams = filelib:wildcard(filename:join([BaseDir, "**", "ebin", "*.beam"])),
Deps = lists:foldl(fun(Beam, Acc) -> gb_sets:insert(Beam, Acc) end,
gb_sets:empty(), Beams),
Mods = gb_sets:fold(fun(Beam, Acc) -> gather_deps(Xref, Beam) ++ Acc end,
[], Deps),
lists:usort(Mods).
-spec gather_deps(atom(), string()) -> app_calls().
gather_deps(Xref, FileName) ->
XrefOpts = [{verbose, 'false'},
{builtins, 'true'},
{warnings, false}],
case xref:add_module(Xref, FileName, XrefOpts) of
{ok, Mod} ->
Query = "XC",
{ok, Calls} = xref:q(Xref, Query),
ok = xref:remove_module(Xref, Mod),
[ToModule
|| {_From, {ToModule,_,_}} <- Calls];
{error, MissingMod, {no_such_module,_}} ->
out_stderr("Missing ~p\n", [MissingMod]),
[];
{error, NoDbgMod, {no_debug_info,_Path}} ->
out_stderr("~p has no debug info, skipping\n", [NoDbgMod]),
[];
{error, Mod, Error} ->
out_stderr("Error in ~p: ~p\n", [Mod, Error]),
[]
end.
-spec all_erlang_apps() -> erl_apps().
all_erlang_apps() ->
[{beam_to_app(Path), find_beams(Path)}
|| Path <- code:get_path(), Path =/= "."].
-compile({inline,
[
{find_beams, 1},
{beams_to_mods, 1},
{beam_to_mod, 1},
{beam_to_app, 1},
{app_vsn_to_app, 1}
]}).
beam_to_app(Beam) ->
%% $ERL_ROOT/lib/App-Vsn/ebin -> AppVsn
AppVsn = filename:basename(filename:dirname(Beam)),
app_vsn_to_app(AppVsn).
find_beams(EbinPath) ->
beams_to_mods(filelib:wildcard(filename:join(EbinPath, "*.beam"))).
beams_to_mods(Beams) ->
[beam_to_mod(Beam) || Beam <- Beams].
beam_to_mod(Beam) ->
list_to_atom(filename:basename(Beam, ".beam")).
app_vsn_to_app(AppVsn) ->
[App, _Vsn] = string:tokens(AppVsn, "-"),
list_to_atom(App).
check_dirs_exist(Dirs) ->
lists:foldl(fun(Dir, Acc) ->
case filelib:is_dir(Dir) of
true ->
[Dir | Acc];
false ->
out_stderr("Skipping missing directory [~s]\n",
[Dir]),
Acc
end
end, [], Dirs).
usage() ->
out_stderr("usage: ~s path [path...]\n\n"
"Find OTP dependencies of .beam files "
"(compiled with debug info) in list of paths\n",
[filename:basename(escript:script_name())]),
halt(1).
out_stderr(Fmt, Args) ->
io:format(standard_error, Fmt, Args).
%% ex: ft=erlang ts=4 sts=4 sw=4 et