Custom Compiler Modules

This is a new feature in rebar3 3.7.0 which allows to write custom compilers to be used with Rebar3. It is useful whenever you have files of a different language that you want to build alongside Erlang resources.

This interface is currently used internally for .xrl, .yrl, and .mib files, but currently few plugins have tried it.

🚧This is an unstable interface

Since we have not had many plugin authors try this interface yet, it is marked as unstable and is suspect to change.

We are looking for help of contributors to further stabilize it before marking it stable. You should use this if you are willing to enter in contact with us to help iterate on the features available in Custom Compiler Plugins

It is possible that your custom compiler requires something more complex. For example, the facilities provided by this interface are insufficient to build projects that run with mix as a buildtool, and the plugin for that uses a custom compiler plugin

Current Interface

The following callbacks are defined:

  1. -type extension() :: string().
  2. -type out_mappings() :: [{extension(), file:filename()}].
  3. %% Returns context that the rebar3 compiler needs regarding the project
  4. %% and files supported by this compiler
  5. -callback context(rebar_app_info:t()) ->
  6. %% source directories to look into for files
  7. #{src_dirs => [file:dirname()],
  8. %% directory for include files
  9. include_dirs => [file:dirname()],
  10. %% file extension for the source files (".yrl")
  11. src_ext => extension(),
  12. %% mapping of output extensions to output directories.
  13. %% For example, .yrl files are compiled to .erl files
  14. %% and so the outmapping is [{".erl", "path/to/app/src/"}]
  15. out_mappings => out_mappings()}.
  16. %% Files required to be built in any specific priority before others
  17. %% directed graph of application names
  18. -callback needed_files(digraph:graph(),
  19. %% files found by the rebar3 compiler matching the context
  20. [file:filename()],
  21. %% previously provided filetype to directory mappings
  22. out_mappings(),
  23. %% Information about the current OTP app
  24. rebar_app_info:t()) ->
  25. %% list of files that need to be built first, with their options.
  26. %% used for things like parse transforms, for example
  27. {{[file:filename()], term()},
  28. %% list of files that can be built last, with their options
  29. {[file:filename()], term()}}.
  30. %% Specify file which are required dependencies of the current file,
  31. %% allowing the compiler to build a graph of build order required.
  32. %% An example for this might be include files.
  33. %% If no specific dependencies exist (any order is good), return
  34. %% an empty list.
  35. -callback dependencies(Source :: file:filename(),
  36. SourceDir :: file:dirname(),
  37. Directories :: [file:dirname()]) ->
  38. [file:filename()].
  39. %% Compile the actual file
  40. %% file to compile
  41. -callback compile(file:filename(),
  42. %% mappings defined earlier for directories for each file type
  43. out_mappings(),
  44. %% a dictionary of rebar3 configuration values
  45. rebar_dict(),
  46. %% compiler-specific options list
  47. list()) ->
  48. ok
  49. | {ok, [string()]} % same but with filenames
  50. | {ok, [string()], [string()]}. % same but with filenames & warnings
  51. %% Clean up after built files
  52. %% files found by compiler, app-specific info
  53. -callback clean([file:filename()], rebar_app_info:t()) -> _.

Initializing a Compiler Module as a Plugin

Register the compiler module in the same place where you would register a custom compiler plugin:

  1. %% Note: the name of the module matches the name of the plugin application
  2. -module(my_compiler_plugin).
  3. -export([init/1]).
  4. %% Called when rebar3 first boots, before even parsing the arguments
  5. %% or commands to be run. Purely initiates the provider, and nothing
  6. %% else should be done here.
  7. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  8. init(State) ->
  9. %% Optional:
  10. %% Provider = providers:create([Options]),
  11. %% State1 = rebar_state:add_provider(State, Provider),
  12. %% This adds the new compiler module:
  13. State1 = rebar_state:append_compilers(State, [my_compiler_mod]),
  14. %% If needing the new compiler module to take precedence over
  15. %% other ones (i.e. generating .erl files from another format):
  16. State2 = rebar_state:append_compilers(State1, [translator_mod]),
  17. {ok, State2}.