aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/src/guide/dialyzer.asciidoc71
-rw-r--r--plugins/dialyzer.mk19
-rw-r--r--test/plugin_dialyzer.mk242
3 files changed, 326 insertions, 6 deletions
diff --git a/doc/src/guide/dialyzer.asciidoc b/doc/src/guide/dialyzer.asciidoc
index 7377c82..d58afbc 100644
--- a/doc/src/guide/dialyzer.asciidoc
+++ b/doc/src/guide/dialyzer.asciidoc
@@ -1,5 +1,72 @@
== Dialyzer
-// @todo Write it.
+Dialyzer is a tool that will detect discrepancies in your
+program. It does so using a technique known as success
+typing analysis which has the advantage of providing no
+false positives. Dialyzer is able to detect type errors,
+dead code and more.
-Placeholder chapter.
+Erlang.mk provides a wrapper around Dialyzer.
+
+=== How it works
+
+Dialyzer requires a PLT file to work. The PLT file contains
+the analysis information from all applications which are not
+expected to change, or rarely do. These would be all the
+dependencies of the application or applications you are
+currently working on, including standard applications in
+Erlang/OTP itself.
+
+Dialyzer can generate this PLT file. Erlang.mk includes rules
+to automatically generate the PLT file when it is missing.
+
+Once the PLT file is generated, Dialyzer can perform the
+analysis in record time.
+
+=== Configuration
+
+In a typical usage scenario, no variable needs to be set.
+The defaults should be enough. Do note however that the
+dependencies need to be set properly using the `DEPS` and
+`LOCAL_DEPS` variables.
+
+The `DIALYZER_PLT` file indicates where the PLT file will
+be written to (and read from). By default this is
+'$(PROJECT).plt' in the project's directory. Note that
+the `DIALYZER_PLT` variable is exported and is understood
+by Dialyzer directly.
+
+The `PLT_APPS` variable can be used to add additional
+applications to the PLT. You can either list application
+names or paths to these applications.
+
+Erlang.mk defines two variables for specifying options
+for the analysis: `DIALYZER_DIRS` and `DIALYZER_OPTS`.
+The former one defines which directories should be part
+of the analysis. The latter defines what extra warnings
+Dialyzer should report.
+
+Note that Erlang.mk enables the race condition warnings
+by default. As it can take considerably large resources
+to run, you may want to disable it on larger projects.
+
+=== Usage
+
+To perform an analysis, run the following command:
+
+[source,bash]
+$ make dialyze
+
+This will create the PLT file if it doesn't exist.
+
+The analysis will also be performed when you run the
+following command, alongside tests:
+
+[source,bash]
+$ make check
+
+You can use the `plt` target to create the PLT file if
+it doesn't exist. This is normally not necessary as
+Dialyzer creates it automatically.
+
+The PLT file will be removed when you run `make distclean`.
diff --git a/plugins/dialyzer.mk b/plugins/dialyzer.mk
index 709b43f..8c2710f 100644
--- a/plugins/dialyzer.mk
+++ b/plugins/dialyzer.mk
@@ -9,9 +9,8 @@ DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt
export DIALYZER_PLT
PLT_APPS ?=
-DIALYZER_DIRS ?= --src -r src
-DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \
- -Wunmatched_returns # -Wunderspecs
+DIALYZER_DIRS ?= --src -r $(wildcard src) $(ALL_APPS_DIRS)
+DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions -Wunmatched_returns # -Wunderspecs
# Core targets.
@@ -27,6 +26,18 @@ help::
# Plugin-specific targets.
+define filter_opts.erl
+ Opts = binary:split(<<"$1">>, <<"-">>, [global]),
+ Filtered = lists:reverse(lists:foldl(fun
+ (O = <<"pa ", _/bits>>, Acc) -> [O|Acc];
+ (O = <<"D ", _/bits>>, Acc) -> [O|Acc];
+ (O = <<"I ", _/bits>>, Acc) -> [O|Acc];
+ (_, Acc) -> Acc
+ end, [], Opts)),
+ io:format("~s~n", [[["-", O] || O <- Filtered]]),
+ halt().
+endef
+
$(DIALYZER_PLT): deps app
$(verbose) dialyzer --build_plt --apps erts kernel stdlib $(PLT_APPS) $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS)
@@ -40,4 +51,4 @@ dialyze:
else
dialyze: $(DIALYZER_PLT)
endif
- $(verbose) dialyzer --no_native $(DIALYZER_DIRS) $(DIALYZER_OPTS)
+ $(verbose) dialyzer --no_native `$(call erlang,$(call filter_opts.erl,$(ERLC_OPTS)))` $(DIALYZER_DIRS) $(DIALYZER_OPTS)
diff --git a/test/plugin_dialyzer.mk b/test/plugin_dialyzer.mk
new file mode 100644
index 0000000..29c8285
--- /dev/null
+++ b/test/plugin_dialyzer.mk
@@ -0,0 +1,242 @@
+# Dialyzer plugin.
+
+DIALYZER_CASES = app apps-only apps-with-local-deps check custom-plt deps erlc-opts local-deps opts plt-apps
+DIALYZER_TARGETS = $(addprefix dialyzer-,$(DIALYZER_CASES))
+DIALYZER_CLEAN_TARGETS = $(addprefix clean-,$(DIALYZER_TARGETS))
+
+.PHONY: dialyzer $(C_SRC_TARGETS) clean-dialyzer $(DIALYZER_CLEAN_TARGETS)
+
+clean-dialyzer: $(DIALYZER_CLEAN_TARGETS)
+
+$(DIALYZER_CLEAN_TARGETS):
+ $t rm -rf $(APP_TO_CLEAN)/
+
+dialyzer: $(DIALYZER_TARGETS)
+
+dialyzer-app: build clean-dialyzer-app
+
+ $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 Dialyzer"
+ $t $(MAKE) -C $(APP) dialyze $v
+
+ $i "Check that the PLT file was created"
+ $t test -f $(APP)/.$(APP).plt
+
+ $i "Create a module with a function that has no local return"
+ $t printf "%s\n" \
+ "-module(warn_me)." \
+ "doit() -> 1 = 2, ok." > $(APP)/src/warn_me.erl
+
+ $i "Confirm that Dialyzer errors out"
+ $t ! $(MAKE) -C $(APP) dialyze $v
+
+ $i "Distclean the application"
+ $t $(MAKE) -C $(APP) distclean $v
+
+ $i "Check that the PLT file was removed"
+ $t test ! -e $(APP)/.$(APP).plt
+
+dialyzer-apps-only: build clean-dialyzer-apps-only
+
+ $i "Create a multi application repository with no root application"
+ $t mkdir $(APP)/
+ $t cp ../erlang.mk $(APP)/
+ $t echo "include erlang.mk" > $(APP)/Makefile
+
+ $i "Create a new application my_app"
+ $t $(MAKE) -C $(APP) new-app in=my_app $v
+
+ $i "Create a module my_server from gen_server template in my_app"
+ $t $(MAKE) -C $(APP) new t=gen_server n=my_server in=my_app $v
+
+ $i "Add Cowlib to the list of dependencies"
+ $t perl -ni.bak -e 'print;if ($$.==1) {print "DEPS = cowlib\n"}' $(APP)/apps/my_app/Makefile
+
+ $i "Run Dialyzer"
+ $t $(MAKE) -C $(APP) dialyze $v
+
+ $i "Check that the PLT file was created automatically"
+ $t test -f $(APP)/.$(APP).plt
+
+ $i "Confirm that Cowlib was included in the PLT"
+ $t dialyzer --plt_info --plt $(APP)/.$(APP).plt | grep -q cowlib
+
+ $i "Create a module with a function that has no local return"
+ $t printf "%s\n" \
+ "-module(warn_me)." \
+ "doit() -> 1 = 2, ok." > $(APP)/apps/my_app/src/warn_me.erl
+
+ $i "Confirm that Dialyzer errors out"
+ $t ! $(MAKE) -C $(APP) dialyze $v
+
+dialyzer-apps-with-local-deps: build clean-dialyzer-apps-with-local-deps
+
+ $i "Create a multi application repository with no root application"
+ $t mkdir $(APP)/
+ $t cp ../erlang.mk $(APP)/
+ $t echo "include erlang.mk" > $(APP)/Makefile
+
+ $i "Create a new application my_app"
+ $t $(MAKE) -C $(APP) new-app in=my_app $v
+
+ $i "Create a new application my_core_app"
+ $t $(MAKE) -C $(APP) new-app in=my_core_app $v
+
+ $i "Add my_core_app to the list of local dependencies for my_app"
+ $t perl -ni.bak -e 'print;if ($$.==1) {print "LOCAL_DEPS = my_core_app\n"}' $(APP)/apps/my_app/Makefile
+
+ $i "Run Dialyzer"
+ $t $(MAKE) -C $(APP) dialyze $v
+
+ $i "Check that the PLT file was created automatically"
+ $t test -f $(APP)/.$(APP).plt
+
+ $i "Confirm that my_core_app was NOT included in the PLT"
+ $t ! dialyzer --plt_info --plt $(APP)/.$(APP).plt | grep -q my_core_app
+
+dialyzer-check: build clean-dialyzer-check
+
+ $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 'make check'"
+ $t $(MAKE) -C $(APP) check $v
+
+ $i "Check that the PLT file was created"
+ $t test -f $(APP)/.$(APP).plt
+
+ $i "Create a module with a function that has no local return"
+ $t printf "%s\n" \
+ "-module(warn_me)." \
+ "doit() -> 1 = 2, ok." > $(APP)/src/warn_me.erl
+
+ $i "Confirm that Dialyzer errors out on 'make check'"
+ $t ! $(MAKE) -C $(APP) check $v
+
+dialyzer-custom-plt: build clean-dialyzer-custom-plt
+
+ $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 "Set a custom DIALYZER_PLT location"
+ $t perl -ni.bak -e 'print;if ($$.==1) {print "DIALYZER_PLT = custom.plt\n"}' $(APP)/Makefile
+
+ $i "Run Dialyzer"
+ $t $(MAKE) -C $(APP) dialyze $v
+
+ $i "Check that the PLT file was created"
+ $t test -f $(APP)/custom.plt
+
+ $i "Distclean the application"
+ $t $(MAKE) -C $(APP) distclean $v
+
+ $i "Check that the PLT file was removed"
+ $t test ! -e $(APP)/custom.plt
+
+dialyzer-deps: build clean-dialyzer-deps
+
+ $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 "Add Cowlib to the list of dependencies"
+ $t perl -ni.bak -e 'print;if ($$.==1) {print "DEPS = cowlib\n"}' $(APP)/Makefile
+
+ $i "Run Dialyzer"
+ $t $(MAKE) -C $(APP) dialyze $v
+
+ $i "Check that the PLT file was created"
+ $t test -f $(APP)/.$(APP).plt
+
+ $i "Confirm that Cowlib was included in the PLT"
+ $t dialyzer --plt_info --plt $(APP)/.$(APP).plt | grep -q cowlib
+
+dialyzer-erlc-opts: build clean-dialyzer-erlc-opts
+
+ $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 header file in a non-standard directory"
+ $t mkdir $(APP)/exotic/
+ $t touch $(APP)/exotic/dialyze.hrl
+
+ $i "Create a module that includes this header"
+ $t printf "%s\n" \
+ "-module(no_warn)." \
+ "-export([doit/0])." \
+ "-include(\"dialyze.hrl\")." \
+ "doit() -> ok." > $(APP)/src/no_warn.erl
+
+ $i "Point ERLC_OPTS to the non-standard include directory"
+ $t perl -ni.bak -e 'print;if ($$.==1) {print "ERLC_OPTS += -I exotic\n"}' $(APP)/Makefile
+
+ $i "Run Dialyzer"
+ $t $(MAKE) -C $(APP) dialyze $v
+
+dialyzer-local-deps: build clean-dialyzer-local-deps
+
+ $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 "Add runtime_tools to the list of local dependencies"
+ $t perl -ni.bak -e 'print;if ($$.==1) {print "LOCAL_DEPS = runtime_tools\n"}' $(APP)/Makefile
+
+ $i "Build the PLT"
+ $t $(MAKE) -C $(APP) plt $v
+
+ $i "Confirm that runtime_tools was included in the PLT"
+ $t dialyzer --plt_info --plt $(APP)/.$(APP).plt | grep -q runtime_tools
+
+dialyzer-opts: build clean-dialyzer-opts
+
+ $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 "Make Dialyzer save output to a file"
+ $t perl -ni.bak -e 'print;if ($$.==1) {print "DIALYZER_OPTS = -o output.txt\n"}' $(APP)/Makefile
+
+ $i "Create a module with a function that has no local return"
+ $t printf "%s\n" \
+ "-module(warn_me)." \
+ "-export([doit/0])." \
+ "doit() -> gen_tcp:connect(a, b, c), ok." > $(APP)/src/warn_me.erl
+
+ $i "Run Dialyzer"
+ $t ! $(MAKE) -C $(APP) dialyze $v
+
+ $i "Check that the PLT file was created"
+ $t test -f $(APP)/.$(APP).plt
+
+ $i "Check that the output file was created"
+ $t test -f $(APP)/output.txt
+
+dialyzer-plt-apps: build clean-dialyzer-plt-apps
+
+ $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 "Add runtime_tools to PLT_APPS"
+ $t perl -ni.bak -e 'print;if ($$.==1) {print "PLT_APPS = runtime_tools\n"}' $(APP)/Makefile
+
+ $i "Build the PLT"
+ $t $(MAKE) -C $(APP) plt $v
+
+ $i "Confirm that runtime_tools was included in the PLT"
+ $t dialyzer --plt_info --plt $(APP)/.$(APP).plt | grep -q runtime_tools