From f257d5557d8858edf6ed965bfd8cadc54f74dca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Fri, 20 May 2022 14:41:11 +0200 Subject: Revamp and document Xref support --- CHANGELOG.asciidoc | 10 ++ core/erlc.mk | 2 +- doc/src/guide/xref.asciidoc | 181 +++++++++++++++++++++++++- plugins/xref.mk | 222 +++++++++++++++++++++++++++---- test/plugin_xref.mk | 308 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 698 insertions(+), 25 deletions(-) create mode 100644 test/plugin_xref.mk diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 03232ba..936aa25 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -25,3 +25,13 @@ {dev_mode, false}. {include_erts, true}. ``` + +2022/05/31: Xref support has been rewritten. Erlang.mk no + longer uses the xref_runner, instead implementing + its own interface. This new interface is more + flexible and more powerful: it supports both + checks and informational analyses as well as + the Xref query functions that use the powerful + Xref language to perform custom queries. Erlang.mk + can also run analyses and queries against all + dependencies as well as Erlang/OTP applications. diff --git a/core/erlc.mk b/core/erlc.mk index 71ba5b9..1377869 100644 --- a/core/erlc.mk +++ b/core/erlc.mk @@ -5,7 +5,7 @@ # Configuration. -ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \ +ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars \ +warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec COMPILE_FIRST ?= COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST))) diff --git a/doc/src/guide/xref.asciidoc b/doc/src/guide/xref.asciidoc index 44ed190..725f40c 100644 --- a/doc/src/guide/xref.asciidoc +++ b/doc/src/guide/xref.asciidoc @@ -1,6 +1,183 @@ [[xref]] == Xref -// @todo Write it. +Xref is a cross reference tool for analyzing dependencies +between functions, modules, applications and releases. +Erlang.mk provides an interface to analyzing all except +the releases. -Placeholder chapter. +Both predefined checks and custom queries are supported +in Erlang.mk. + +=== Usage + +To run Xref with the default predefined checks: + +[source,bash] +$ make xref + +Erlang.mk will error out when warnings are found. + +The following predefined checks can be used: + +* `undefined_function_calls` +* `undefined_functions` (similar to the previous) +* `locals_not_used` (detected by the compiler) +* `exports_not_used` +* `deprecated_function_calls` (also detected by the compiler) +* `deprecated_functions` (similar to the previous) +* `{deprecated_function_calls, Flag}` +* `{deprecated_functions, Flag}` (similar to the previous) + +Erlang.mk will only run the `undefined_function_calls` +check by default. + +To change the check the `XREF_CHECKS` variable can be used: + +[source,bash] +$ make xref XREF_CHECKS=exports_not_used + +Multiple checks can be run at once. The checks variable +must be defined as an Erlang list: + +[source,bash] +$ make xref XREF_CHECKS="[undefined_function_calls, exports_not_used]" + +Erlang.mk also supports informational analyses. Those will +not error out when results are found, since they are not +errors. To find all modules that call `cowboy_req` functions: + +[source,bash] +$ make xref XREF_CHECKS="{module_use, cowboy_req}" + +The following informational checks are supported: + +* `{call, MFA}` - functions that MFA calls +* `{use, MFA}` - functions that call MFA +* `{module_call, Mod}` - modules that Mod calls (Mod depends on them) +* `{module_use, Mod}` - modules that call Mod (they depend on Mod) +* `{application_call, App}` - apps that App calls (App depends on them) +* `{application_use, App}` - apps that call App (they depend on App) + +The scope might need to be increased in order to obtain +the complete results of informational checks. This is +especially true for module and applications, with +application results being dependent on the applications +being added to the scope to be found. + +=== Queries + +Erlang.mk provides an interface to the Xref query +functions. To perform a query, the `q` variable +must be used instead of `XREF_CHECKS`. For example, +to obtain all unresolved calls: + +[source,bash] +$ make xref q=UC + +The query language is documented at the top of the +link:https://www.erlang.org/doc/man/xref.html[XRef manual page]. + +=== Analysis scope + +Erlang.mk will set the scope of analysis to the current +project by default. The scope can be automatically +extended to the applications from multi-application +repositories, to dependencies and to the built-in +Erlang/OTP applications themselves. + +To change the scope, the `XREF_SCOPE` variable can be +set. The variable can either be set in your Makefile +or from the command line. The following values can +be defined: + +* `app` - the current project +* `apps` - applications from multi-application repositories +* `deps` - dependencies +* `otp` - Erlang/OTP applications + +To get the most complete analysis possible they should +all be added to the variable: + +[source,bash] +---- +$ make xref XREF_CHECKS="{application_use, ssl}" XREF_SCOPE="app apps deps otp" +Application ssl is used by: +- my_app +- diameter +- eldap +- ftp +- inets +- ssl +---- + +Additional applications can be provided using the +`XREF_EXTRA_APP_DIRS` variable. Note that these +applications will need to be compiled before they +can be found by Xref. + +Similarly, non-application directories can be +added using `XREF_EXTRA_DIRS`. The directory +to be provided must be the one that contains +the beam files. + +=== Ignoring warnings + +Sometimes it is necessary to ignore warnings because +they are expected. This is the case for example +when multiple Erlang/OTP versions must be supported +and modules or functions have been added or removed. + +Erlang.mk supports both project-wide configuration +and Rebar-compatible inline ignores. To ignore +warnings for a function in the current module the +following line can be added to the source file: + +[source,erlang] +---- +-ignore_xref({log, 1}). +---- + +The module name can be specified explicitly: + +[source,erlang] +---- +-ignore_xref({my_mod, log, 1}). +---- + +As well as a full module can be ignored: + +[source,erlang] +---- +-ignore_xref(my_mod). +---- + +The ignored functions can be provided as a list: + +[source,erlang] +---- +-ignore_xref([{log, 1}, {pretty_print, 1}]). +---- + +The `XREF_IGNORE` variable can be used to define +functions and modules to ignore project-wide. It +accepts either MFAs or modules: + +[source,make] +XREF_IGNORE={my_mod, log, 1} + +Multiple ignores must be provided as an Erlang +list: + +[source,make] +XREF_IGNORE=[{my_mod, log, 1}, other_mod] + +By default Erlang.mk will ignore unused exports +for behavior callbacks when the `exports_not_used` +check is run. It is possible to override this +behavior, or to ignore the callbacks for queries +and other checks, by defining the `XREF_IGNORE_CALLBACKS` +variable: + +[source,bash] +$ make xref XREF_CHECKS=exports_not_used XREF_IGNORE_CALLBACKS=0 diff --git a/plugins/xref.mk b/plugins/xref.mk index 7da0f37..6c7cd5b 100644 --- a/plugins/xref.mk +++ b/plugins/xref.mk @@ -1,39 +1,217 @@ -# Copyright (c) 2016, Loïc Hoguin -# Copyright (c) 2015, Erlang Solutions Ltd. +# Copyright (c) 2022, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. -.PHONY: xref distclean-xref +.PHONY: xref # Configuration. -ifeq ($(XREF_CONFIG),) - XREFR_ARGS := -else - XREFR_ARGS := -c $(XREF_CONFIG) -endif +# We do not use locals_not_used or deprecated_function_calls +# because the compiler will error out by default in those +# cases with Erlang.mk. Deprecated functions may make sense +# in some cases but few libraries define them. We do not +# use exports_not_used by default because it hinders more +# than it helps library projects such as Cowboy. Finally, +# undefined_functions provides little that undefined_function_calls +# doesn't already provide, so it's not enabled by default. +XREF_CHECKS ?= [undefined_function_calls] + +# Instead of predefined checks a query can be evaluated +# using the Xref DSL. The $q variable is used in that case. + +# The scope is a list of keywords that correspond to +# application directories, being essentially an easy way +# to configure which applications to analyze. With: +# +# - app: ../$(PROJECT) +# - apps: $(ALL_APPS_DIRS) +# - deps: $(ALL_DEPS_DIRS) +# - otp: Built-in Erlang/OTP applications. +# +# The default is conservative (app) and will not be +# appropriate for all types of queries (for example +# application_call requires adding all applications +# that might be called or they will not be found). +XREF_SCOPE ?= app # apps deps otp + +# If the above is not enough, additional application +# directories can be configured. +XREF_EXTRA_APP_DIRS ?= -XREFR ?= $(CURDIR)/xrefr -export XREFR +# As well as additional non-application directories. +XREF_EXTRA_DIRS ?= -XREFR_URL ?= https://github.com/inaka/xref_runner/releases/download/1.1.0/xrefr +# Erlang.mk supports -ignore_xref([...]) with forms +# {M, F, A} | {F, A} | M, the latter ignoring whole +# modules. Ignores can also be provided project-wide. +XREF_IGNORE ?= [] + +# All callbacks may be ignored. Erlang.mk will ignore +# them automatically for exports_not_used (unless it +# is explicitly disabled by the user). +XREF_IGNORE_CALLBACKS ?= # Core targets. help:: $(verbose) printf '%s\n' '' \ 'Xref targets:' \ - ' xref Run Xrefr using $$XREF_CONFIG as config file if defined' - -distclean:: distclean-xref + ' xref Analyze the project using Xref' \ + ' xref q=QUERY Evaluate an Xref query' # Plugin-specific targets. -$(XREFR): - $(gen_verbose) $(call core_http_get,$(XREFR),$(XREFR_URL)) - $(verbose) chmod +x $(XREFR) - -xref: deps app $(XREFR) - $(gen_verbose) $(XREFR) $(XREFR_ARGS) +define xref.erl + {ok, Xref} = xref:start([]), + Scope = [$(call comma_list,$(XREF_SCOPE))], + AppDirs0 = [$(call comma_list,$(foreach d,$(XREF_EXTRA_APP_DIRS),"$d"))], + AppDirs1 = case lists:member(otp, Scope) of + false -> AppDirs0; + true -> + RootDir = code:root_dir(), + AppDirs0 ++ [filename:dirname(P) || P <- code:get_path(), lists:prefix(RootDir, P)] + end, + AppDirs2 = case lists:member(deps, Scope) of + false -> AppDirs1; + true -> [$(call comma_list,$(foreach d,$(ALL_DEPS_DIRS),"$d"))] ++ AppDirs1 + end, + AppDirs3 = case lists:member(apps, Scope) of + false -> AppDirs2; + true -> [$(call comma_list,$(foreach d,$(ALL_APPS_DIRS),"$d"))] ++ AppDirs2 + end, + AppDirs = case lists:member(app, Scope) of + false -> AppDirs3; + true -> ["../$(PROJECT)"|AppDirs3] + end, + [{ok, _} = xref:add_application(Xref, AppDir, [{builtins, true}]) || AppDir <- AppDirs], + ExtraDirs = [$(call comma_list,$(foreach d,$(XREF_EXTRA_DIRS),"$d"))], + [{ok, _} = xref:add_directory(Xref, ExtraDir, [{builtins, true}]) || ExtraDir <- ExtraDirs], + ok = xref:set_library_path(Xref, code:get_path() -- (["ebin", "."] ++ AppDirs ++ ExtraDirs)), + Checks = case {$1, is_list($2)} of + {check, true} -> $2; + {check, false} -> [$2]; + {query, _} -> [$2] + end, + FinalRes = [begin + IsInformational = case $1 of + query -> true; + check -> + is_tuple(Check) andalso + lists:member(element(1, Check), + [call, use, module_call, module_use, application_call, application_use]) + end, + {ok, Res0} = case $1 of + check -> xref:analyze(Xref, Check); + query -> xref:q(Xref, Check) + end, + Res = case IsInformational of + true -> Res0; + false -> + lists:filter(fun(R) -> + {Mod, MFA} = case R of + {MFA0 = {M, _, _}, _} -> {M, MFA0}; + {M, _, _} -> {M, R} + end, + Attrs = try + Mod:module_info(attributes) + catch error:undef -> + [] + end, + InlineIgnores = lists:flatten([ + [case V of + M when is_atom(M) -> {M, '_', '_'}; + {F, A} -> {Mod, F, A}; + _ -> V + end || V <- Values] + || {ignore_xref, Values} <- Attrs]), + BuiltinIgnores = [ + {eunit_test, wrapper_test_exported_, 0} + ], + DoCallbackIgnores = case {Check, "$(strip $(XREF_IGNORE_CALLBACKS))"} of + {exports_not_used, ""} -> true; + {_, "0"} -> false; + _ -> true + end, + CallbackIgnores = case DoCallbackIgnores of + false -> []; + true -> + Behaviors = lists:flatten([ + [BL || {behavior, BL} <- Attrs], + [BL || {behaviour, BL} <- Attrs] + ]), + [{Mod, CF, CA} || B <- Behaviors, {CF, CA} <- B:behaviour_info(callbacks)] + end, + WideIgnores = if + is_list($(XREF_IGNORE)) -> + [if is_atom(I) -> {I, '_', '_'}; true -> I end + || I <- $(XREF_IGNORE)]; + true -> [$(XREF_IGNORE)] + end, + Ignores = InlineIgnores ++ BuiltinIgnores ++ CallbackIgnores ++ WideIgnores, + not (lists:member(MFA, Ignores) + orelse lists:member({Mod, '_', '_'}, Ignores)) + end, Res0) + end, + case Res of + [] -> ok; + _ when IsInformational -> + case Check of + {call, {CM, CF, CA}} -> + io:format("Functions that ~s:~s/~b calls:~n", [CM, CF, CA]); + {use, {CM, CF, CA}} -> + io:format("Function ~s:~s/~b is called by:~n", [CM, CF, CA]); + {module_call, CMod} -> + io:format("Modules that ~s calls:~n", [CMod]); + {module_use, CMod} -> + io:format("Module ~s is used by:~n", [CMod]); + {application_call, CApp} -> + io:format("Applications that ~s calls:~n", [CApp]); + {application_use, CApp} -> + io:format("Application ~s is used by:~n", [CApp]); + _ when $1 =:= query -> + io:format("Query ~s returned:~n", [Check]) + end, + [case R of + {{InM, InF, InA}, {M, F, A}} -> + io:format("- ~s:~s/~b called by ~s:~s/~b~n", + [M, F, A, InM, InF, InA]); + {M, F, A} -> + io:format("- ~s:~s/~b~n", [M, F, A]); + ModOrApp -> + io:format("- ~s~n", [ModOrApp]) + end || R <- Res], + ok; + _ -> + [case {Check, R} of + {undefined_function_calls, {{InM, InF, InA}, {M, F, A}}} -> + io:format("Undefined function ~s:~s/~b called by ~s:~s/~b~n", + [M, F, A, InM, InF, InA]); + {undefined_functions, {M, F, A}} -> + io:format("Undefined function ~s:~s/~b~n", [M, F, A]); + {locals_not_used, {M, F, A}} -> + io:format("Unused local function ~s:~s/~b~n", [M, F, A]); + {exports_not_used, {M, F, A}} -> + io:format("Unused exported function ~s:~s/~b~n", [M, F, A]); + {deprecated_function_calls, {{InM, InF, InA}, {M, F, A}}} -> + io:format("Deprecated function ~s:~s/~b called by ~s:~s/~b~n", + [M, F, A, InM, InF, InA]); + {deprecated_functions, {M, F, A}} -> + io:format("Deprecated function ~s:~s/~b~n", [M, F, A]); + _ -> + io:format("~p: ~p~n", [Check, R]) + end || R <- Res], + error + end + end || Check <- Checks], + stopped = xref:stop(Xref), + case lists:usort(FinalRes) of + [ok] -> halt(0); + _ -> halt(1) + end +endef -distclean-xref: - $(gen_verbose) rm -rf $(XREFR) +xref: deps app +ifdef q + $(verbose) $(call erlang,$(call xref.erl,query,"$q"),-pa ebin/) +else + $(verbose) $(call erlang,$(call xref.erl,check,$(XREF_CHECKS)),-pa ebin/) +endif diff --git a/test/plugin_xref.mk b/test/plugin_xref.mk new file mode 100644 index 0000000..06b145b --- /dev/null +++ b/test/plugin_xref.mk @@ -0,0 +1,308 @@ +# Xref plugin. + +XREF_TARGETS = $(call list_targets,xref) + +.PHONY: xref $(XREF_TARGETS) + +xref: $(XREF_TARGETS) + +xref-check: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Run the Xref plugin" + $t $(MAKE) -C $(APP) xref $v + + $i "Create a module with an undefined function call" + $t printf "%s\n" \ + "-module(bad)." \ + "-export([f/0])." \ + "f() -> this_module:does_not_exist()." \ + > $(APP)/src/bad.erl + + $i "Run the Xref plugin again, expect an error" + $t ! $(MAKE) -C $(APP) xref $v + +xref-check-custom: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Run the Xref plugin with undefined_functions" + $t $(MAKE) -C $(APP) xref XREF_CHECKS=undefined_functions $v + + $i "Create a module with an unused export" + $t printf "%s\n" \ + "-module(bad1)." \ + "-export([f/0])." \ + "f() -> whereis(user) ! bad_message." \ + > $(APP)/src/bad1.erl + + $i "Run the Xref plugin with exports_not_used, expect an error" + $t ! $(MAKE) -C $(APP) xref XREF_CHECKS=exports_not_used $v + + $i "Run the Xref plugin with multiple checks" + $t $(MAKE) -C $(APP) xref XREF_CHECKS="[undefined_function_calls, undefined_functions]" $v + + $i "Create a module with an undefined function call" + $t printf "%s\n" \ + "-module(bad2)." \ + "-export([f/0])." \ + "f() -> this_module:does_not_exist()." \ + > $(APP)/src/bad2.erl + + $i "Run the Xref plugin with multiple checks, expect an error" + $t ! $(MAKE) -C $(APP) xref XREF_CHECKS="[undefined_function_calls, undefined_functions]" $v + +xref-check-informational: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Run the Xref plugin with module_use" + $t $(MAKE) -C $(APP) xref XREF_CHECKS="{module_use, $(APP)_sup}" > $(APP)/output.txt + + $i "Confirm that the module was found" + $t grep -q "\- $(APP)_app$$" $(APP)/output.txt + +xref-query: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Run the Xref plugin with query XC (external calls)" + $t $(MAKE) -C $(APP) xref q="XC" > $(APP)/output.txt + + $i "Confirm that the supervisor:start_link/3 call was found" + $t grep -q "\- supervisor:start_link/3 called by $(APP)_sup:start_link/0$$" $(APP)/output.txt + +xref-scope-apps: init + + $i "Bootstrap a new OTP library named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap-lib $v + + $i "Create a new library my_app" + $t $(MAKE) -C $(APP) new-lib in=my_app $v + + $i "Create a module with an undefined function call inside my_app" + $t printf "%s\n" \ + "-module(bad2)." \ + "-export([f/0])." \ + "f() -> this_module:does_not_exist()." \ + > $(APP)/apps/my_app/src/bad2.erl + + $i "Run the Xref plugin with apps in the scope, expect an error" + $t ! $(MAKE) -C $(APP) xref XREF_SCOPE="app apps" $v + +xref-scope-deps: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Bootstrap a new OTP library named my_dep inside $(APP)" + $t mkdir $(APP)/my_dep + $t cp ../erlang.mk $(APP)/my_dep/ + $t $(MAKE) -C $(APP)/my_dep/ -f erlang.mk bootstrap-lib $v + + $i "Create a module with an undefined function call inside my_dep" + $t printf "%s\n" \ + "-module(bad2)." \ + "-export([f/0])." \ + "f() -> this_module:does_not_exist()." \ + > $(APP)/my_dep/src/bad2.erl + + $i "Add my_dep to the list of dependencies" + $t perl -ni.bak -e 'print;if ($$.==1) {print "DEPS = my_dep\ndep_my_dep = cp $(CURDIR)/$(APP)/my_dep/\n"}' $(APP)/Makefile + +ifdef LEGACY + $i "Add my_dep to the applications key in the .app.src file" + $t perl -ni.bak -e 'print;if ($$.==7) {print "\t\tmy_dep,\n"}' $(APP)/src/$(APP).app.src +endif + + $i "Run the Xref plugin with deps in the scope, expect an error" + $t ! $(MAKE) -C $(APP) xref XREF_SCOPE="app deps" $v + +xref-scope-otp: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Run the Xref plugin for module use with OTP in the scope" + $t $(MAKE) -C $(APP) xref XREF_CHECKS="{module_use, asn1ct_pretty_format}" \ + XREF_SCOPE="app otp" > $(APP)/output.txt + + $i "Confirm that the asn1ct_pretty_format module use was analysed" + $t grep -q "\- asn1ct_pretty_format$$" $(APP)/output.txt + +xref-extra-apps: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Bootstrap a new OTP library named extra_app inside $(APP)" + $t mkdir $(APP)/extra_app + $t cp ../erlang.mk $(APP)/extra_app/ + $t $(MAKE) -C $(APP)/extra_app/ -f erlang.mk bootstrap-lib $v + + $i "Create a module in extra_app with a function call to $(APP)" + $t printf "%s\n" \ + "-module(extra)." \ + "-export([f/0])." \ + "f() -> $(APP)_sup:init([])." \ + > $(APP)/extra_app/src/extra.erl + + $i "Build extra_app" + $t $(MAKE) -C $(APP)/extra_app $v + + $i "Run the Xref plugin for application use with the extra app" + $t $(MAKE) -C $(APP) xref XREF_CHECKS="{application_use, $(APP)}" \ + XREF_EXTRA_APP_DIRS="extra_app/" > $(APP)/output.txt + + $i "Confirm that the extra_app application call was found" + $t grep -q "\- extra_app$$" $(APP)/output.txt + +xref-extra-dirs: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Bootstrap a new OTP library named extra_dir inside $(APP)" + $t mkdir $(APP)/extra_dir + $t cp ../erlang.mk $(APP)/extra_dir/ + $t $(MAKE) -C $(APP)/extra_dir/ -f erlang.mk bootstrap-lib $v + + $i "Create a module in extra_dir with an undefined function call" + $t printf "%s\n" \ + "-module(bad)." \ + "-export([f/0])." \ + "f() -> this_module:does_not_exist()." \ + > $(APP)/extra_dir/src/bad.erl + + $i "Build extra_dir" + $t $(MAKE) -C $(APP)/extra_dir $v + + $i "Run the Xref plugin with the extra dir, expect an error" + $t ! $(MAKE) -C $(APP) xref XREF_EXTRA_DIRS="extra_dir/ebin/" $v + +xref-ignore-inline-fa: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Create a module with an undefined function call and an inline ignore" + $t printf "%s\n" \ + "-module(bad)." \ + "-export([f/0])." \ + "-ignore_xref([{f,0}])." \ + "f() -> this_module:does_not_exist()." \ + > $(APP)/src/bad.erl + + $i "Run the Xref plugin, expect success" + $t $(MAKE) -C $(APP) xref $v + +xref-ignore-inline-mfa: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Create a module with an undefined function call and an inline ignore" + $t printf "%s\n" \ + "-module(bad)." \ + "-export([f/0])." \ + "-ignore_xref([{bad,f,0}])." \ + "f() -> this_module:does_not_exist()." \ + > $(APP)/src/bad.erl + + $i "Run the Xref plugin, expect success" + $t $(MAKE) -C $(APP) xref $v + +xref-ignore-inline-mod: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Create a module with an undefined function call and an inline ignore" + $t printf "%s\n" \ + "-module(bad)." \ + "-export([f/0])." \ + "-ignore_xref(?MODULE)." \ + "f() -> this_module:does_not_exist()." \ + > $(APP)/src/bad.erl + + $i "Run the Xref plugin, expect success" + $t $(MAKE) -C $(APP) xref $v + +xref-ignore-project-wide-mfa: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Create a module with an undefined function call" + $t printf "%s\n" \ + "-module(bad)." \ + "-export([f/0])." \ + "f() -> this_module:does_not_exist()." \ + > $(APP)/src/bad.erl + + $i "Run the Xref plugin with project-wide ignore, expect success" + $t $(MAKE) -C $(APP) xref XREF_IGNORE="{bad,f,0}" $v + +xref-ignore-project-wide-mod: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Create a module with an undefined function call" + $t printf "%s\n" \ + "-module(bad)." \ + "-export([f/0])." \ + "f() -> this_module:does_not_exist()." \ + > $(APP)/src/bad.erl + + $i "Run the Xref plugin with project-wide ignore, expect success" + $t $(MAKE) -C $(APP) xref XREF_IGNORE="[bad]" $v + +xref-ignore-callbacks: init + + $i "Bootstrap a new OTP application named $(APP)" + $t mkdir $(APP)/ + $t cp ../erlang.mk $(APP)/ + $t $(MAKE) -C $(APP) -f erlang.mk bootstrap $v + + $i "Run the Xref plugin for exports_not_used, expect success" + $t $(MAKE) -C $(APP) xref XREF_CHECKS="exports_not_used" $v + + $i "Run the Xref plugin again with explicit ignoring of callbacks, expect success" + $t $(MAKE) -C $(APP) xref XREF_CHECKS="exports_not_used" XREF_IGNORE_CALLBACKS=1 $v + + $i "Run the Xref plugin again without ignoring callbacks, expect an error" + $t ! $(MAKE) -C $(APP) xref XREF_CHECKS="exports_not_used" XREF_IGNORE_CALLBACKS=0 $v -- cgit v1.2.3