aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2022-05-20 14:41:11 +0200
committerLoïc Hoguin <[email protected]>2022-05-30 15:28:36 +0200
commitf257d5557d8858edf6ed965bfd8cadc54f74dca7 (patch)
tree6ed4c7b289588a70ef96bc1970df680043c2db9e
parentd114c854427c8967b3cad82e1b0ef79309682a90 (diff)
downloaderlang.mk-f257d5557d8858edf6ed965bfd8cadc54f74dca7.tar.gz
erlang.mk-f257d5557d8858edf6ed965bfd8cadc54f74dca7.tar.bz2
erlang.mk-f257d5557d8858edf6ed965bfd8cadc54f74dca7.zip
Revamp and document Xref support
-rw-r--r--CHANGELOG.asciidoc10
-rw-r--r--core/erlc.mk2
-rw-r--r--doc/src/guide/xref.asciidoc181
-rw-r--r--plugins/xref.mk222
-rw-r--r--test/plugin_xref.mk308
5 files changed, 698 insertions, 25 deletions
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 <[email protected]>
-# Copyright (c) 2015, Erlang Solutions Ltd.
+# Copyright (c) 2022, Loïc Hoguin <[email protected]>
# 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