mk/xrefr: Provide our own xrefr(1) escript
It is forked from xref_runner. Compared to upstream, it allows to
exclude entire modules: we need that for the *_compat modules.
Furthermore it's compatible with Erlang R16B03 because:
- It's a plain text escript, not a precompiled one.
- It doesn't use maps.
[#140125673]
This commit is contained in:
parent
a6fc135a2a
commit
c42e53a6e0
|
|
@ -3,7 +3,9 @@
|
|||
# --------------------------------------------------------------------
|
||||
|
||||
ifneq ($(PROJECT),rabbit_common)
|
||||
XREFR_ARGS := --config $(DEPS_DIR)/rabbit_common/xref.config
|
||||
XREFR := $(DEPS_DIR)/rabbit_common/mk/xrefr
|
||||
else
|
||||
XREFR := mk/xrefr
|
||||
endif
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -0,0 +1,213 @@
|
|||
#!/usr/bin/env escript
|
||||
%% vim:ft=erlang:
|
||||
|
||||
%% The code is copied from xref_runner.
|
||||
%% https://github.com/inaka/xref_runner
|
||||
%%
|
||||
%% The only change is the possibility to exclude entire modules because
|
||||
%% we don't want to analyse the *_compat modules.
|
||||
%%
|
||||
%% It's also a plain text escript instead of a compiled one because we
|
||||
%% want to support Erlang R16B03 and the version of xref_runner uses
|
||||
%% maps and is built with something like Erlang 18.
|
||||
|
||||
%% This mode allows us to reference local function. For instance:
|
||||
%% lists:map(fun generate_comment/1, Comments)
|
||||
-mode(compile).
|
||||
|
||||
-define(DIRS, ["ebin", "test"]).
|
||||
-define(EXCLUDED_MODULES, [rand_compat,
|
||||
ssl_compat,
|
||||
time_compat]).
|
||||
|
||||
-define(CHECKS, [undefined_function_calls,
|
||||
undefined_functions,
|
||||
locals_not_used]).
|
||||
|
||||
main(_) ->
|
||||
Checks = ?CHECKS,
|
||||
XrefWarnings = lists:append([check(Check) || Check <- Checks]),
|
||||
warnings_prn(XrefWarnings),
|
||||
case XrefWarnings of
|
||||
[] -> ok;
|
||||
_ -> halt(1)
|
||||
end.
|
||||
|
||||
check(Check) ->
|
||||
Dirs = ?DIRS,
|
||||
lists:foreach(fun code:add_path/1, Dirs),
|
||||
|
||||
{ok, Xref} = xref:start([]),
|
||||
try
|
||||
ok = xref:set_library_path(Xref, code:get_path()),
|
||||
|
||||
lists:foreach(
|
||||
fun(Dir) ->
|
||||
{ok, _} = xref:add_directory(Xref, Dir)
|
||||
end, Dirs),
|
||||
|
||||
ExcludedModules = ?EXCLUDED_MODULES,
|
||||
xref:remove_module(Xref, ExcludedModules),
|
||||
|
||||
{ok, Results} = xref:analyze(Xref, Check),
|
||||
|
||||
FilteredResults = filter_xref_results(Check, Results),
|
||||
|
||||
[result_to_warning(Check, Result) || Result <- FilteredResults]
|
||||
after
|
||||
stopped = xref:stop(Xref)
|
||||
end.
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Filtering results.
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
filter_xref_results(Check, Results) ->
|
||||
SourceModules =
|
||||
lists:usort([source_module(Result) || Result <- Results]),
|
||||
|
||||
Ignores =
|
||||
lists:flatmap(
|
||||
fun(Module) -> get_ignorelist(Module, Check) end, SourceModules),
|
||||
|
||||
[Result || Result <- Results,
|
||||
not lists:member(parse_xref_result(Result), Ignores)].
|
||||
|
||||
source_module({Mt, _Ft, _At}) -> Mt;
|
||||
source_module({{Ms, _Fs, _As}, _Target}) -> Ms.
|
||||
|
||||
%%
|
||||
%% Ignore behaviour functions, and explicitly marked functions
|
||||
%%
|
||||
%% Functions can be ignored by using
|
||||
%% -ignore_xref([{F, A}, {M, F, A}...]).
|
||||
get_ignorelist(Mod, Check) ->
|
||||
%% Get ignore_xref attribute and combine them in one list
|
||||
Attributes =
|
||||
try
|
||||
Mod:module_info(attributes)
|
||||
catch
|
||||
_Class:_Error -> []
|
||||
end,
|
||||
|
||||
IgnoreXref =
|
||||
[mfa(Mod, Value) || {ignore_xref, Values} <- Attributes, Value <- Values],
|
||||
|
||||
BehaviourCallbacks = get_behaviour_callbacks(Check, Mod, Attributes),
|
||||
|
||||
%% And create a flat {M, F, A} list
|
||||
IgnoreXref ++ BehaviourCallbacks.
|
||||
|
||||
get_behaviour_callbacks(exports_not_used, Mod, Attributes) ->
|
||||
Behaviours = [Value || {behaviour, Values} <- Attributes, Value <- Values],
|
||||
[{Mod, {Mod, F, A}}
|
||||
|| B <- Behaviours, {F, A} <- B:behaviour_info(callbacks)];
|
||||
get_behaviour_callbacks(_Check, _Mod, _Attributes) ->
|
||||
[].
|
||||
|
||||
mfa(M, {F, A}) -> {M, {M, F, A}};
|
||||
mfa(M, MFA) -> {M, MFA}.
|
||||
|
||||
parse_xref_result({{SM, _, _}, MFAt}) -> {SM, MFAt};
|
||||
parse_xref_result({TM, _, _} = MFAt) -> {TM, MFAt}.
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Preparing results.
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
result_to_warning(Check, {MFASource, MFATarget}) ->
|
||||
{Filename, Line} = get_source(MFASource),
|
||||
[{filename, Filename},
|
||||
{line, Line},
|
||||
{source, MFASource},
|
||||
{target, MFATarget},
|
||||
{check, Check}];
|
||||
result_to_warning(Check, MFA) ->
|
||||
{Filename, Line} = get_source(MFA),
|
||||
[{filename, Filename},
|
||||
{line, Line},
|
||||
{source, MFA},
|
||||
{check, Check}].
|
||||
|
||||
%%
|
||||
%% Given a MFA, find the file and LOC where it's defined. Note that
|
||||
%% xref doesn't work if there is no abstract_code, so we can avoid
|
||||
%% being too paranoid here.
|
||||
%%
|
||||
get_source({M, F, A}) ->
|
||||
case code:get_object_code(M) of
|
||||
error -> {"", 0};
|
||||
{M, Bin, _} -> find_function_source(M, F, A, Bin)
|
||||
end.
|
||||
|
||||
find_function_source(M, F, A, Bin) ->
|
||||
AbstractCode = beam_lib:chunks(Bin, [abstract_code]),
|
||||
{ok, {M, [{abstract_code, {raw_abstract_v1, Code}}]}} = AbstractCode,
|
||||
|
||||
%% Extract the original source filename from the abstract code
|
||||
[Source|_] = [S || {attribute, _, file, {S, _}} <- Code],
|
||||
|
||||
%% Extract the line number for a given function def
|
||||
Fn = [E || E <- Code,
|
||||
element(1, E) == function,
|
||||
element(3, E) == F,
|
||||
element(4, E) == A],
|
||||
|
||||
case Fn of
|
||||
[{function, Line, F, _, _}] when is_integer(Line) ->
|
||||
{Source, Line};
|
||||
[{function, Line, F, _, _}] ->
|
||||
{Source, erl_anno:line(Line)};
|
||||
%% do not crash if functions are exported, even though they
|
||||
%% are not in the source.
|
||||
%% parameterized modules add new/1 and instance/1 for example.
|
||||
[] -> {Source, 0}
|
||||
end.
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
%% Reporting results.
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
warnings_prn([]) ->
|
||||
ok;
|
||||
warnings_prn(Comments) ->
|
||||
Messages = lists:map(fun generate_comment/1, Comments),
|
||||
lists:foreach(fun warning_prn/1, Messages).
|
||||
|
||||
warning_prn(Message) ->
|
||||
FullMessage = Message ++ "~n",
|
||||
io:format(FullMessage, []).
|
||||
|
||||
generate_comment(XrefWarning) ->
|
||||
Filename = proplists:get_value(filename, XrefWarning),
|
||||
Line = proplists:get_value(line, XrefWarning),
|
||||
Source = proplists:get_value(source, XrefWarning),
|
||||
Check = proplists:get_value(check, XrefWarning),
|
||||
Target = proplists:get_value(target, XrefWarning),
|
||||
Position = case {Filename, Line} of
|
||||
{"", _} -> "";
|
||||
{Filename, 0} -> [Filename, " "];
|
||||
{Filename, Line} -> [Filename, ":",
|
||||
integer_to_list(Line), " "]
|
||||
end,
|
||||
[Position, generate_comment_text(Check, Source, Target)].
|
||||
|
||||
generate_comment_text(Check, {SM, SF, SA}, TMFA) ->
|
||||
SMFA = io_lib:format("`~p:~p/~p`", [SM, SF, SA]),
|
||||
generate_comment_text(Check, SMFA, TMFA);
|
||||
generate_comment_text(Check, SMFA, {TM, TF, TA}) ->
|
||||
TMFA = io_lib:format("`~p:~p/~p`", [TM, TF, TA]),
|
||||
generate_comment_text(Check, SMFA, TMFA);
|
||||
|
||||
generate_comment_text(undefined_function_calls, SMFA, TMFA) ->
|
||||
io_lib:format("~s calls undefined function ~s", [SMFA, TMFA]);
|
||||
generate_comment_text(undefined_functions, SMFA, _TMFA) ->
|
||||
io_lib:format("~s is not defined as a function", [SMFA]);
|
||||
generate_comment_text(locals_not_used, SMFA, _TMFA) ->
|
||||
io_lib:format("~s is an unused local function", [SMFA]);
|
||||
generate_comment_text(exports_not_used, SMFA, _TMFA) ->
|
||||
io_lib:format("~s is an unused export", [SMFA]);
|
||||
generate_comment_text(deprecated_function_calls, SMFA, TMFA) ->
|
||||
io_lib:format("~s calls deprecated function ~s", [SMFA, TMFA]);
|
||||
generate_comment_text(deprecated_functions, SMFA, _TMFA) ->
|
||||
io_lib:format("~s is deprecated", [SMFA]).
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
% vim:ft=erlang:sw=2:et:
|
||||
|
||||
[
|
||||
{xref, [
|
||||
{checks, [undefined_function_calls,
|
||||
undefined_functions,
|
||||
locals_not_used
|
||||
%exports_not_used,
|
||||
%deprecated_function_calls,
|
||||
%deprecated_functions
|
||||
]},
|
||||
{config, #{
|
||||
dirs => ["ebin", "test"],
|
||||
exclude_modules => [rand_compat,
|
||||
ssl_compat,
|
||||
time_compat]
|
||||
}}
|
||||
]
|
||||
}
|
||||
].
|
||||
Loading…
Reference in New Issue