Erlang: find cross-app calls using xref
At work, we use the multi-app project pattern to organize our codebase. This lets us track everything in a single repository but still keep things isolated.
For isolation, we wanted to restrict apps to only be able to call the public interfaces of other apps (similar to facade pattern). However, since everything in Erlang is in a global namespace, nothing prevents code in one app to call the (exported) functions from another app.
Next best solution—detect the above scenario and raise warnings during code review/CI.
Xref to the rescue:
Xref is a cross reference tool that can be used for finding dependencies between functions, modules, applications and releases.
Xref includes some predefined analysis patterns that perform some common tasks like searching for undefined functions, deprecated function calls, unused exported functions, etc.
How it works: when xref server is started and some modules/applications/releases are added for analysis, it builds a Call Graph: a directed graph data structure containing the calls between functions, modules, applications or releases. It also creates an Inter Call Graph which holds information about indirect calls (chain of calls). It exposes a very powerful query language, which can be used to extract any information we want from the above graph data structures.
To demonstrate this, I created a sample multi-app repository: library_sample. There are some cross-app function calls in this code that we want to detect.
This repo is supposed to represent the functionality of a physical Library. It has four apps:
library_catalog has metadata about the books in the library,
library_inventory has information about the availability of books, return dates, etc.,
library_api has HTTP handlers which call the above, and
library is the main app which brings it all together.
Let’s say we want that
library_api can call
library_inventory functions, but catalog and inventory cannot call each other directly.
First, we clone the repo and run rebar3 shell:
Then, we start xref and add our build directory for analysis:
xref:q/2 for querying the constructed call graph:
This means that there are no direct calls from the
library_inventory application to the
library_catalog application. But, there is a direct call from
E | library_catalog || library_inventory can be read as:
E= All Call Graph Edges
|= The subset of calls from any of the vertices. So
| library_catalogcreates a subset which contains calls from the
||= The subset of calls to any of the vertices. So,
|| library_inventoryfurther creates a subset of the previous subset which contains calls to the
To get both direct and indirect calls,
closure E has to be used:
This tells us that there is an indirect direct call from
The query language is very powerful, and there are more interesting examples in the xref user’s guide.
this only runs the required queries manually in Erlang shell. We want
to be able to run it in continuous integration. Luckily, rebar3 comes
with a way to specify custom xref queries to run when running
./rebar3 xref, and to raise an error if they don’t match against the expected value defined.
Here’s the xref section from my
This performs the two queries I want and matches them against the the target value of
. Sample output:
So, now this is ready for automation.