Building C/C++

🚧No port compiler

In rebar3 it is required to have a Makefile or other instructions for building your C/C++ code outside of rebar itself.

Using the Makefile Template

We’ll start by making a new lib named test_nif and then using the cmake template from the root of the test_nif project.

  1. $ rebar3 new lib test_nif
  2. ===> Writing test_nif/src/test_nif.erl
  3. ===> Writing test_nif/src/test_nif.app.src
  4. ===> Writing test_nif/rebar.config
  5. ===> Writing test_nif/.gitignore
  6. ===> Writing test_nif/LICENSE
  7. ===> Writing test_nif/README.md
  8. $ cd test_nif
  9. $ rebar3 new cmake
  10. ===> Writing c_src/Makefile

In test_nif's rebar.config, add the pre_hooks line so that make is called when compile is run. Furthermore, add the post_hooks entry for cleaning up the built C object files.

The Makefile written by rebar3 new cmake is a GNU Makefile, which means you will need to have GNU Make installed on the system. In the example, we provide a handler for the FreeBSD operating system, which assumes GNU Make is called gmake.

  1. {pre_hooks,
  2. [{"(linux|darwin|solaris)", compile, "make -C c_src"},
  3. {"(freebsd)", compile, "gmake -C c_src"}]}.
  4. {post_hooks,
  5. [{"(linux|darwin|solaris)", clean, "make -C c_src clean"},
  6. {"(freebsd)", clean, "gmake -C c_src clean"}]}.

Below is a NIF which has a function repeat that will take a pid and an Erlang term to send to that pid.

  1. #include "erl_nif.h"
  2. ERL_NIF_TERM
  3. mk_atom(ErlNifEnv* env, const char* atom)
  4. {
  5. ERL_NIF_TERM ret;
  6. if(!enif_make_existing_atom(env, atom, &ret, ERL_NIF_LATIN1))
  7. {
  8. return enif_make_atom(env, atom);
  9. }
  10. return ret;
  11. }
  12. ERL_NIF_TERM
  13. mk_error(ErlNifEnv* env, const char* mesg)
  14. {
  15. return enif_make_tuple2(env, mk_atom(env, "error"), mk_atom(env, mesg));
  16. }
  17. static ERL_NIF_TERM
  18. repeat(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
  19. {
  20. ErlNifEnv* msg_env;
  21. ErlNifPid pid;
  22. ERL_NIF_TERM copy;
  23. if(argc != 2)
  24. {
  25. return enif_make_badarg(env);
  26. }
  27. if(!enif_is_pid(env, argv[0]))
  28. {
  29. return mk_error(env, "not_a_pid");
  30. }
  31. if(!enif_get_local_pid(env, argv[0], &pid))
  32. {
  33. return mk_error(env, "not_a_local_pid");
  34. }
  35. msg_env = enif_alloc_env();
  36. if(msg_env == NULL)
  37. {
  38. return mk_error(env, "environ_alloc_error");
  39. }
  40. copy = enif_make_copy(msg_env, argv[1]);
  41. if(!enif_send(env, &pid, msg_env, copy))
  42. {
  43. enif_free(msg_env);
  44. return mk_error(env, "error_sending_term");
  45. }
  46. enif_free_env(msg_env);
  47. return mk_atom(env, "ok");
  48. }
  49. static ErlNifFunc nif_funcs[] = {
  50. {"repeat", 2, repeat}
  51. };
  52. ERL_NIF_INIT(test_nif, nif_funcs, NULL, NULL, NULL, NULL);

Modify test_nif.erl to load the test_nif shared library from priv and export repeat/2.

  1. -module(test_nif).
  2. -export([repeat/2]).
  3. -on_load(init/0).
  4. -define(APPNAME, test_nif).
  5. -define(LIBNAME, test_nif).
  6. repeat(_, _) ->
  7. not_loaded(?LINE).
  8. init() ->
  9. SoName = case code:priv_dir(?APPNAME) of
  10. {error, bad_name} ->
  11. case filelib:is_dir(filename:join(["..", priv])) of
  12. true ->
  13. filename:join(["..", priv, ?LIBNAME]);
  14. _ ->
  15. filename:join([priv, ?LIBNAME])
  16. end;
  17. Dir ->
  18. filename:join(Dir, ?LIBNAME)
  19. end,
  20. erlang:load_nif(SoName, 0).
  21. not_loaded(Line) ->
  22. erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}).

Note that the error() function will cause Dializer errors. Using erlang:nif_error/1 will not, and is preferred here.

Run rebar3 shell and give the NIF a try.

  1. $ rebar3 shell
  2. ===> Verifying dependencies...
  3. ===> Compiling test_nif
  4. Erlang/OTP 17 [erts-6.3] [source] [64-bit] [smp:4:4] [async-threads:0] [kernel-poll:false]
  5. Eshell V6.3 (abort with ^G)
  6. 1> test_nif:repeat(self(), hello).
  7. ok
  8. 2> receive X -> X end.
  9. hello

References