Custom behaviour undefined (rebar3 + erlang_ls)

Hello, all.

Could you help me, please?
The question is that
After the user-defined behavior is implemented, the server outputs, for example

3 -behavior(gen_example). % W: behavior gen_example undefined

An example from here: Implement a new Erlang OTP behavior
Findings:

  • Cowboy behaviour undefined #1044
    Workaround from here: Add the following to the local erlang_ls.config file (see https://erlang-ls.github.io/configuration/)
    code_path_extra_dirs:
      - "./_build/default/lib/*/ebin/"
    
    This workaround doesn’t work for me.
    ❯ grep -HRs "\-behavior" .
    ./test/handlers/stream_handler_h.erl:-behavior(cowboy_stream).
    ./test/handlers/ws_init_h.erl:-behavior(cowboy_websocket).
    ./test/handlers/ws_dont_validate_utf8_h.erl:-behavior(cowboy_websocket).
    ./test/handlers/ws_info_commands_h.erl:-behavior(cowboy_websocket).
    ./test/handlers/ws_terminate_h.erl:-behavior(cowboy_websocket).
    ./test/handlers/ws_active_commands_h.erl:-behavior(cowboy_websocket).
    ./test/handlers/ws_deflate_opts_h.erl:-behavior(cowboy_websocket).
    ./test/handlers/ws_init_commands_h.erl:-behavior(cowboy_websocket).
    ./test/handlers/ws_shutdown_reason_commands_h.erl:-behavior(cowboy_websocket).
    ./test/handlers/ws_deflate_commands_h.erl:-behavior(cowboy_websocket).
    ./test/handlers/ws_ping_h.erl:-behavior(cowboy_websocket).
    ./test/handlers/ws_handle_commands_h.erl:-behavior(cowboy_websocket).
    ./test/handlers/ws_set_options_commands_h.erl:-behavior(cowboy_websocket).
    ./src/cowboy_stream_h.erl:-behavior(cowboy_stream).
    ./src/cowboy_compress_h.erl:-behavior(cowboy_stream).
    ./src/cowboy_decompress_h.erl:-behavior(cowboy_stream).
    ./src/cowboy_tracer_h.erl:-behavior(cowboy_stream).
    ./src/cowboy_tls.erl:-behavior(ranch_protocol).
    ./src/cowboy_metrics_h.erl:-behavior(cowboy_stream).
    ./src/cowboy_clear.erl:-behavior(ranch_protocol).
    ./plugins.mk:-behavior(cowboy_handler).
    ./plugins.mk:-behavior(cowboy_loop).
    ./plugins.mk:-behavior(cowboy_rest).
    ./plugins.mk:-behavior(cowboy_websocket).
    
        4 -module(ws_handle_commands_h).                                                  
    W 5 -behavior(cowboy_websocket). % W: behaviour cowboy_websocket undefined                                                                                                                                        
        6  Issue in included file (18): behaviour cowboy_sub_protocol undefined           
        7  (Compiler L0000)  
    
  • Custom behaviours not recognized #8
    For example from here:
    W   3 -behaviour(bhv). % W: behaviour bhv undefined 
    
  • intellij-erlang
    From here aaronlelevier claims that
    " This happened to me because I didn’t correctly use -callback."
    "
    In the examples above, the definitions of the β€˜callback’ attribute correspond to so that
    "
    The contracts specified with -callback attributes in behaviour modules can be further refined by adding -spec attributes in callback modules. This can be useful as -callback contracts are usually generic.
    …
    Each -spec contract is to be a subtype of the respective -callback contract.
    "
    See 6.3 User-Defined Behaviours
  • How to create and use a custom Erlang behavior?
    The same thing happens for an example from here
    W   2 -behavior(my_behavior). % W: behaviour my_behavior undefined  
    
  • Emakefile - custom behaviour undefined
    I suppose this workaround is suitable for this case, but it doesn’t work for me.
    code_path_extra_dirs:
      - "./_build/default/lib/*/ebin/"
    
  • β€œWarning: behaviour X undefined”, one release different apps #1444
    I have 2 app, but one is written in C, and the other in Erlang.
    Therefore, I think this workaround is not suitable for me:
    "
    Have you ensured that app2 has app1 in its .app.src’s applications tuple? That order is used by the compiler to ensure proper compilation order.
    "

Maybe it has something to do with Artifacts?

What I use:

  • Erlang/OTP 27.0 Release Candidate 2

  • Rebar3

    ❯ rebar3 -v
    rebar 3.22.0 on Erlang/OTP 27 Erts 14.3
    
  • erlang_ls

    ❯ erlang_ls -v
    Version: 0.49.0
    
  • local erlang_ls.config for rebar3 umbrella project from here https://erlang-ls.github.io/configuration/

    ❯ cat erlang_ls.config
    apps_dirs:
      - "apps/*"
    deps_dirs:
      - "_build/default/lib/*"
    include_dirs:
      - "apps"
      - "apps/*/include"
      - "_build/default/lib/"
      - "_build/default/lib/*/include"
    code_path_extra_dirs:
      - "_build/default/lib/*/ebin/"
    

I would be grateful for answers.

1 Like

Have you defined a module for the behaviours you define? Each behaviour needs a module with the same name containing the callbacks and at least the function behaviour_info/1 which should have at least one clause:

behaviour_info(callbacks) ->
    [{callback_1,2},
     {callback_2,1},
     ...];

This is what the compiler looks for when it checks your code. It will still compile even if it can’t find it but it warns you and then it is up to you to make sure the module exists.

2 Likes

Hello, Robert.

Q: "Have you defined a module for the behaviours you define? "
A: Yes it is.
I have an umbrella project (Rebar3).
Structure of folders:

myproj
β”œβ”€β”€ apps
β”‚   β”œβ”€β”€ c_app
β”‚   └── erl_app
β”‚       β”œβ”€β”€ c_src
β”‚       β”œβ”€β”€ include
β”‚       └── src
β”‚           β”œβ”€β”€ some_folder
β”‚           β”‚   β”œβ”€β”€ x.erl        % callback module
β”‚           β”‚   └── x_server.erl % behavior module
β”‚           β”œβ”€β”€ myproj_app.erl
β”‚           β”œβ”€β”€ myproj.app.src
β”‚           └── myproj_sup.erl
β”œβ”€β”€ config
β”‚   β”œβ”€β”€ sys.config
β”‚   └── vm.args
β”œβ”€β”€ doc
β”œβ”€β”€ erlang_ls.config
β”œβ”€β”€ LICENSE.md
β”œβ”€β”€ priv
β”‚   β”œβ”€β”€ bin
β”‚   └── lib
β”œβ”€β”€ README.md
β”œβ”€β”€ rebar.config
β”œβ”€β”€ rebar.lock
└── test
    └── myproj_tests.erl

It says here Directory Structure β€œAdditional sub-directories within src can be used as namespaces to organize source files. These directories should never be deeper than one level.”
This corresponds to the condition:

...
β”‚       └── src
β”‚           β”œβ”€β”€ some_folder
β”‚           β”‚   β”œβ”€β”€ x.erl        % callback module
β”‚           β”‚   └── x_server.erl % behavior module

I variant
x_server.erl:

-module(x_server). 
...
-export([behaviour_info/1])
...
behaviour_info(callbacks) ->                                                    
    [{test_cb, 0}].
...

x.erl:

-module(x).
...
-behavior(x_server). % W: behaviour x_server undefined
-export([test_cb/0]).
-spec test_cb() -> atom().                                                      
    test_cb() -> ok.

II variant
x_server.erl:

-module(x_server). 
...
-callback test_cb() -> term().
...

x.erl:

-module(x).
...
-behavior(x_server). % W: behaviour x_server undefined
-export([test_cb/0]).
-spec test_cb() -> atom().                                                      
    test_cb() -> ok.

I can’t understand it:
β€œEach behaviour needs a module with the same name containing the callbacks”.
So the module x_server.erl needs to be renamed to x.erl and put in different folders?

As well about behaviour_info/1
It says here 6.3 User-Defined Behaviours β€œWe recommend using the -callback attribute rather than the behaviour_info() function.”
I have a warning in both cases.

As well about β€œIt will still compile even if it can’t find it”.
Do I understand correctly?
If I compile modules x.erl, x_server.erl and module x_server.erl is not found, will only module x.erl be compiled?
The folder ./_build/default/lib/*/ebin/ contains x.beam and x_server.beam binaries after compilation.

Hello, all.

I finally found a solution a few days later.
Here Code Path:

Environment variable ERL_LIBS (defined in the operating system) can be used to define more library directories to be handled in the same way as the standard OTP library directory described above, except that directories without an ebin directory are ignored.

Now user-defined behavior is defined.
Thanks to everyone who took the time to look at and answer the question.

BR, Denis.

2 Likes

You beat me to my reply. The problem is that the compiler needs to be able to call the behaviour defining module at compile time so it must be compiled first and have it .beam file in the right place so the compiler can find it.

An alternative is make a separate application for the x_server behaviour and have it as a dep.

1 Like

Hello, Robert.

OK.
Thanks.

BR, Denis.