xref.mk 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. # Copyright (c) 2022, Loïc Hoguin <essen@ninenines.eu>
  2. # This file is part of erlang.mk and subject to the terms of the ISC License.
  3. .PHONY: xref
  4. # Configuration.
  5. # We do not use locals_not_used or deprecated_function_calls
  6. # because the compiler will error out by default in those
  7. # cases with Erlang.mk. Deprecated functions may make sense
  8. # in some cases but few libraries define them. We do not
  9. # use exports_not_used by default because it hinders more
  10. # than it helps library projects such as Cowboy. Finally,
  11. # undefined_functions provides little that undefined_function_calls
  12. # doesn't already provide, so it's not enabled by default.
  13. XREF_CHECKS ?= [undefined_function_calls]
  14. # Instead of predefined checks a query can be evaluated
  15. # using the Xref DSL. The $q variable is used in that case.
  16. # The scope is a list of keywords that correspond to
  17. # application directories, being essentially an easy way
  18. # to configure which applications to analyze. With:
  19. #
  20. # - app: ../$(PROJECT)
  21. # - apps: $(ALL_APPS_DIRS)
  22. # - deps: $(ALL_DEPS_DIRS)
  23. # - otp: Built-in Erlang/OTP applications.
  24. #
  25. # The default is conservative (app) and will not be
  26. # appropriate for all types of queries (for example
  27. # application_call requires adding all applications
  28. # that might be called or they will not be found).
  29. XREF_SCOPE ?= app # apps deps otp
  30. # If the above is not enough, additional application
  31. # directories can be configured.
  32. XREF_EXTRA_APP_DIRS ?=
  33. # As well as additional non-application directories.
  34. XREF_EXTRA_DIRS ?=
  35. # Erlang.mk supports -ignore_xref([...]) with forms
  36. # {M, F, A} | {F, A} | M, the latter ignoring whole
  37. # modules. Ignores can also be provided project-wide.
  38. XREF_IGNORE ?= []
  39. # All callbacks may be ignored. Erlang.mk will ignore
  40. # them automatically for exports_not_used (unless it
  41. # is explicitly disabled by the user).
  42. XREF_IGNORE_CALLBACKS ?=
  43. # Core targets.
  44. help::
  45. $(verbose) printf '%s\n' '' \
  46. 'Xref targets:' \
  47. ' xref Analyze the project using Xref' \
  48. ' xref q=QUERY Evaluate an Xref query'
  49. # Plugin-specific targets.
  50. define xref.erl
  51. {ok, Xref} = xref:start([]),
  52. Scope = [$(call comma_list,$(XREF_SCOPE))],
  53. AppDirs0 = [$(call comma_list,$(foreach d,$(XREF_EXTRA_APP_DIRS),"$d"))],
  54. AppDirs1 = case lists:member(otp, Scope) of
  55. false -> AppDirs0;
  56. true ->
  57. RootDir = code:root_dir(),
  58. AppDirs0 ++ [filename:dirname(P) || P <- code:get_path(), lists:prefix(RootDir, P)]
  59. end,
  60. AppDirs2 = case lists:member(deps, Scope) of
  61. false -> AppDirs1;
  62. true -> [$(call comma_list,$(foreach d,$(ALL_DEPS_DIRS),"$d"))] ++ AppDirs1
  63. end,
  64. AppDirs3 = case lists:member(apps, Scope) of
  65. false -> AppDirs2;
  66. true -> [$(call comma_list,$(foreach d,$(ALL_APPS_DIRS),"$d"))] ++ AppDirs2
  67. end,
  68. AppDirs = case lists:member(app, Scope) of
  69. false -> AppDirs3;
  70. true -> ["../$(PROJECT)"|AppDirs3]
  71. end,
  72. [{ok, _} = xref:add_application(Xref, AppDir, [{builtins, true}]) || AppDir <- AppDirs],
  73. ExtraDirs = [$(call comma_list,$(foreach d,$(XREF_EXTRA_DIRS),"$d"))],
  74. [{ok, _} = xref:add_directory(Xref, ExtraDir, [{builtins, true}]) || ExtraDir <- ExtraDirs],
  75. ok = xref:set_library_path(Xref, code:get_path() -- (["ebin", "."] ++ AppDirs ++ ExtraDirs)),
  76. Checks = case {$1, is_list($2)} of
  77. {check, true} -> $2;
  78. {check, false} -> [$2];
  79. {query, _} -> [$2]
  80. end,
  81. FinalRes = [begin
  82. IsInformational = case $1 of
  83. query -> true;
  84. check ->
  85. is_tuple(Check) andalso
  86. lists:member(element(1, Check),
  87. [call, use, module_call, module_use, application_call, application_use])
  88. end,
  89. {ok, Res0} = case $1 of
  90. check -> xref:analyze(Xref, Check);
  91. query -> xref:q(Xref, Check)
  92. end,
  93. Res = case IsInformational of
  94. true -> Res0;
  95. false ->
  96. lists:filter(fun(R) ->
  97. {Mod, MFA} = case R of
  98. {MFA0 = {M, _, _}, _} -> {M, MFA0};
  99. {M, _, _} -> {M, R}
  100. end,
  101. Attrs = try
  102. Mod:module_info(attributes)
  103. catch error:undef ->
  104. []
  105. end,
  106. InlineIgnores = lists:flatten([
  107. [case V of
  108. M when is_atom(M) -> {M, '_', '_'};
  109. {F, A} -> {Mod, F, A};
  110. _ -> V
  111. end || V <- Values]
  112. || {ignore_xref, Values} <- Attrs]),
  113. BuiltinIgnores = [
  114. {eunit_test, wrapper_test_exported_, 0}
  115. ],
  116. DoCallbackIgnores = case {Check, "$(strip $(XREF_IGNORE_CALLBACKS))"} of
  117. {exports_not_used, ""} -> true;
  118. {_, "0"} -> false;
  119. _ -> true
  120. end,
  121. CallbackIgnores = case DoCallbackIgnores of
  122. false -> [];
  123. true ->
  124. Behaviors = lists:flatten([
  125. [BL || {behavior, BL} <- Attrs],
  126. [BL || {behaviour, BL} <- Attrs]
  127. ]),
  128. [{Mod, CF, CA} || B <- Behaviors, {CF, CA} <- B:behaviour_info(callbacks)]
  129. end,
  130. WideIgnores = if
  131. is_list($(XREF_IGNORE)) ->
  132. [if is_atom(I) -> {I, '_', '_'}; true -> I end
  133. || I <- $(XREF_IGNORE)];
  134. true -> [$(XREF_IGNORE)]
  135. end,
  136. Ignores = InlineIgnores ++ BuiltinIgnores ++ CallbackIgnores ++ WideIgnores,
  137. not (lists:member(MFA, Ignores)
  138. orelse lists:member({Mod, '_', '_'}, Ignores))
  139. end, Res0)
  140. end,
  141. case Res of
  142. [] -> ok;
  143. _ when IsInformational ->
  144. case Check of
  145. {call, {CM, CF, CA}} ->
  146. io:format("Functions that ~s:~s/~b calls:~n", [CM, CF, CA]);
  147. {use, {CM, CF, CA}} ->
  148. io:format("Function ~s:~s/~b is called by:~n", [CM, CF, CA]);
  149. {module_call, CMod} ->
  150. io:format("Modules that ~s calls:~n", [CMod]);
  151. {module_use, CMod} ->
  152. io:format("Module ~s is used by:~n", [CMod]);
  153. {application_call, CApp} ->
  154. io:format("Applications that ~s calls:~n", [CApp]);
  155. {application_use, CApp} ->
  156. io:format("Application ~s is used by:~n", [CApp]);
  157. _ when $1 =:= query ->
  158. io:format("Query ~s returned:~n", [Check])
  159. end,
  160. [case R of
  161. {{InM, InF, InA}, {M, F, A}} ->
  162. io:format("- ~s:~s/~b called by ~s:~s/~b~n",
  163. [M, F, A, InM, InF, InA]);
  164. {M, F, A} ->
  165. io:format("- ~s:~s/~b~n", [M, F, A]);
  166. ModOrApp ->
  167. io:format("- ~s~n", [ModOrApp])
  168. end || R <- Res],
  169. ok;
  170. _ ->
  171. [case {Check, R} of
  172. {undefined_function_calls, {{InM, InF, InA}, {M, F, A}}} ->
  173. io:format("Undefined function ~s:~s/~b called by ~s:~s/~b~n",
  174. [M, F, A, InM, InF, InA]);
  175. {undefined_functions, {M, F, A}} ->
  176. io:format("Undefined function ~s:~s/~b~n", [M, F, A]);
  177. {locals_not_used, {M, F, A}} ->
  178. io:format("Unused local function ~s:~s/~b~n", [M, F, A]);
  179. {exports_not_used, {M, F, A}} ->
  180. io:format("Unused exported function ~s:~s/~b~n", [M, F, A]);
  181. {deprecated_function_calls, {{InM, InF, InA}, {M, F, A}}} ->
  182. io:format("Deprecated function ~s:~s/~b called by ~s:~s/~b~n",
  183. [M, F, A, InM, InF, InA]);
  184. {deprecated_functions, {M, F, A}} ->
  185. io:format("Deprecated function ~s:~s/~b~n", [M, F, A]);
  186. _ ->
  187. io:format("~p: ~p~n", [Check, R])
  188. end || R <- Res],
  189. error
  190. end
  191. end || Check <- Checks],
  192. stopped = xref:stop(Xref),
  193. case lists:usort(FinalRes) of
  194. [ok] -> halt(0);
  195. _ -> halt(1)
  196. end
  197. endef
  198. xref: deps app
  199. ifdef q
  200. $(verbose) $(call erlang,$(call xref.erl,query,"$q"),-pa ebin/)
  201. else
  202. $(verbose) $(call erlang,$(call xref.erl,check,$(XREF_CHECKS)),-pa ebin/)
  203. endif