Cowboy build takes too long - is there any way to avoid having to rebuild when I edit src files?

I’m fooling around with Cowboy, and the Getting Started guide has you build the src files with:

% make run

That takes nearly 20 seconds on my system. That’s pretty intolerable when trying to write code. Is there a way to avoid having to rebuild the whole thing when I edit my src files?

1 Like

Does it take 20 seconds for the first run (in the tutorial the make run in the Bootstrap-section), or every time? For the first run it is expected, since it install some dependencies. When you start to change files in your src directory it should be much faster (it only recompiles the changed files).

1 Like

It takes 20 seconds every time I change my code. There is no difference in the build time for the first make run and any subsequent make run. Even if I don’t change anything in my src files, and I execute make run again, it takes 20 seconds.

I’m using macOS 12.5.1.

Erlang version:

% erl
Erlang/OTP 26 [erts-14.1.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Eshell V14.1.1 (press Ctrl+G to abort, type help(). for help)

gmake:

% gmake --version
GNU Make 4.4.1
Built for aarch64-apple-darwin21.6.0
Copyright (C) 1988-2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Here’s the output:

erlang_programs/cowboy_stuff/hello_erlang  gmake run
erl +A1 -noinput -boot no_dot_erlang -pa ebin/ -pz /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/.erlang.mk/rebar3/_build/prod/lib/*/ebin/ -eval "	Config = 	(fun() ->		Config0 =			case file:consult(\"/Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/relx.config\") of				{ok, Terms} ->					Terms;				{error, _} ->					[end,		case filelib:is_file(\"/Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/relx.config.script\") of		true ->				Bindings = erl_eval:add_binding('CONFIG', Config0, erl_eval:new_bindings()),			{ok, Config1} = file:script(\"/Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/relx.config.script\", Bindings),		Config1;			false ->				Config0		end	end)(),	{release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config),	Vsn = case Vsn0 of		{cmd, Cmd} -> os:cmd(Cmd);		semver -> \"\";		{semver, _} -> \"\";		{git, short} -> string:trim(os:cmd(\"git rev-parse --short HEAD\"), both, \"\n\");		{git, long} -> string:trim(os:cmd(\"git rev-parse HEAD\"), both, \"\n\");		VsnStr -> Vsn0	end,	{ok, _} = relx:build_release(#{name => Name, vsn => Vsn}, Config ++ [{output_dir, \"_rel\"}]),	halt(0)." -- erlang.mk
Solving Release hello_erlang_release-1Resolved hello_erlang_release-1release: hello_erlang_release-1
     erts: 14.1.1
     goals: 
          hello_erlang
          sasl
          runtime_tools
     applications: 
          {kernel,"9.1"}
          {stdlib,"5.1.1"}
          {crypto,"5.3"}
          {cowlib,"2.12.1"}
          {asn1,"5.2"}
          {public_key,"1.14.1"}
          {ssl,"11.0.3"}
          {ranch,"1.8.0"}
          {cowboy,"2.10.0"}
          {hello_erlang,"0.1.0"}
          {sasl,"4.2.1"}
          {runtime_tools,"2.0"}
Assembling release hello_erlang_release-1...Release output dir /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_releaseRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/kernel-9.1/ebin/kernel.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/stdlib-5.1.1/ebin/stdlib.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/crypto-5.3/ebin/crypto.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/cowlib-2.12.1/ebin/cowlib.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/asn1-5.2/ebin/asn1.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/public_key-1.14.1/ebin/public_key.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/ssl-11.0.3/ebin/ssl.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/ranch-1.8.0/ebin/ranch.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/cowboy-2.10.0/ebin/cowboy.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/hello_erlang-0.1.0/ebin/hello_erlang.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/sasl-4.2.1/ebin/sasl.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/runtime_tools-2.0/ebin/runtime_tools.appIncluding Erts from /Users/7stud/.asdf/installs/erlang/26.1.2release start script createdRelease successfully assembled: _rel/hello_erlang_releasegmake[1]: Entering directory '/Users/7stud/erlang_programs/cowboy_stuff/hello_erlang'
gmake[1]: Leaving directory '/Users/7stud/erlang_programs/cowboy_stuff/hello_erlang'
erl +A1 -noinput -boot no_dot_erlang -pa ebin/ -pz /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/.erlang.mk/rebar3/_build/prod/lib/*/ebin/ -eval "	Config = 	(fun() ->		Config0 =			case file:consult(\"/Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/relx.config\") of				{ok, Terms} ->					Terms;				{error, _} ->					[end,		case filelib:is_file(\"/Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/relx.config.script\") of		true ->				Bindings = erl_eval:add_binding('CONFIG', Config0, erl_eval:new_bindings()),			{ok, Config1} = file:script(\"/Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/relx.config.script\", Bindings),		Config1;			false ->				Config0		end	end)(),	{release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config),	Vsn = case Vsn0 of		{cmd, Cmd} -> os:cmd(Cmd);		semver -> \"\";		{semver, _} -> \"\";		{git, short} -> string:trim(os:cmd(\"git rev-parse --short HEAD\"), both, \"\n\");		{git, long} -> string:trim(os:cmd(\"git rev-parse HEAD\"), both, \"\n\");		VsnStr -> Vsn0	end,	{ok, _} = relx:build_tar(#{name => Name, vsn => Vsn}, Config ++ [{output_dir, \"_rel\"}]),	halt(0)." -- erlang.mk
Solving Release hello_erlang_release-1Resolved hello_erlang_release-1release: hello_erlang_release-1
     erts: 14.1.1
     goals: 
          hello_erlang
          sasl
          runtime_tools
     applications: 
          {kernel,"9.1"}
          {stdlib,"5.1.1"}
          {crypto,"5.3"}
          {cowlib,"2.12.1"}
          {asn1,"5.2"}
          {public_key,"1.14.1"}
          {ssl,"11.0.3"}
          {ranch,"1.8.0"}
          {cowboy,"2.10.0"}
          {hello_erlang,"0.1.0"}
          {sasl,"4.2.1"}
          {runtime_tools,"2.0"}
Assembling release hello_erlang_release-1...Release output dir /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_releaseRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/kernel-9.1/ebin/kernel.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/stdlib-5.1.1/ebin/stdlib.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/crypto-5.3/ebin/crypto.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/cowlib-2.12.1/ebin/cowlib.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/asn1-5.2/ebin/asn1.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/public_key-1.14.1/ebin/public_key.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/ssl-11.0.3/ebin/ssl.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/ranch-1.8.0/ebin/ranch.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/cowboy-2.10.0/ebin/cowboy.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/hello_erlang-0.1.0/ebin/hello_erlang.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/sasl-4.2.1/ebin/sasl.appRewriting .app file: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib/runtime_tools-2.0/ebin/runtime_tools.appIncluding Erts from /Users/7stud/.asdf/installs/erlang/26.1.2release start script createdRelease successfully assembled: _rel/hello_erlang_releaseBuilding release tarball hello_erlang_release-1.tar.gz...Tarball successfully created: _rel/hello_erlang_release/hello_erlang_release-1.tar.gzExec: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/erts-14.1.1/bin/erlexec -boot /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/releases/1/start -mode embedded -boot_var SYSTEM_LIB_DIR /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/lib -config /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/releases/1/sys.config -args_file /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release/releases/1/vm.args -- console
Root: /Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release
/Users/7stud/erlang_programs/cowboy_stuff/hello_erlang/_rel/hello_erlang_release
heart_beat_kill_pid = 93161
Erlang/OTP 26 [erts-14.1.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Eshell V14.1.1 (press Ctrl+G to abort, type help(). for help)
(hello_erlang@127.0.0.1)1> 

from make help:

Relx targets: run Compile the project, build the release and run it

most of the time is spent in creating the release, in the development phase you do not need that.
Try make shell.
You still have to load&start your application and its dependencies.

I think that’s all what I can contribute since I do not know erlang.mk.

2 Likes

I think you need this library GitHub - rustyio/sync: On-the-fly recompiling and reloading in Erlang. Code without friction.

1 Like

Thanks for the responses.

You still have to load&start your application and its dependencies.

Yes, getting all the paths correct to all the source file dependencies as well as your own source files makes compiling and starting everything non-trivial.

I ended up following a tutorial on setting up cowboy in a Docker container along with rebar3. I’m not using a Docker container, so what I ended up with is a rebar3 app with cowboy as a dependency. Thereafter, when I execute rebar3 shell, that starts a cowboy server and opens a shell in the terminal window. Then if I go into another terminal window and send a request to the url specified in my cowboy code, I get the expected response.

Here are the steps:

$ rebar3 new app my_cowboy_server
$ cd my_cowboy_server

Add cowboy as a dependency–in the file my_cowboy_server/rebar.config:

{erl_opts, [debug_info]}.
{deps, [
  {cowboy, "2.10.0"}
]}.

{shell, [
  % {config, "config/sys.config"},
    {apps, [my_cowboy_server]}
]}.

You don’t have to add ranch or cow_lib as a dependency.

Add cowboy to the list of applications that need to be started before your app–in the file my_cowboy_server/src/my_cowboy.app.src:

{application, my_cowboy_server,
 [{description, "An OTP application"},
  {vsn, "0.1.0"},
  {registered, []},
  {mod, {my_cowboy_server_app, []}},
  {applications,
   [kernel,
    stdlib,
    cowboy
   ]},
  {env,[]},
  {modules, []},

  {licenses, ["Apache-2.0"]},
  {links, []}
 ]}.

Add your routes to the file my_cowboy_server/src/my_cowboy_server_app.erl:

start(_StartType, _StartArgs) ->

    Dispatch = cowboy_router:compile([
        {'_', [{"/greet", hello_handler, []}]}
    ]),

    {ok, _} = cowboy:start_clear(my_http_listener,
        [{port, 8080}],
        #{env => #{dispatch => Dispatch}}
    ),

    my_cowboy_server_sup:start_link().

Create the file my_cowboy_server/src/hello_handler.erl:

-module(hello_handler).
-behavior(cowboy_handler).  

-export([init/2]).

init(Req0, State) ->
    Req = cowboy_req:reply(200,
        #{<<"content-type">> => <<"text/plain">>},
        <<"Hello Erlang!\r\n">>,
        Req0),

	{ok, Req, State}.

Then in one terminal window, I can execute:

my_cowboy_server$ rebar3 shell

and any output in my cowboy code will appear in that terminal window, for instance if I have this code:

-module(my_cowboy_server_app).
-behaviour(application).
-export([start/2, stop/1]).

start(_StartType, _StartArgs) ->
    io:format("[ME]: inside start()~n"),   % <======HERE

    Dispatch = cowboy_router:compile([
        {'_', [{"/greet", hello_handler, []}]}
    ]),

...
...

I see:

my_cowboy_server$ rebar3 shell
rebar3 shell
===> Verifying dependencies...
===> Analyzing applications...
===> Compiling my_cowboy_server
Erlang/OTP 26 [erts-14.1.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Eshell V14.1.1 (press Ctrl+G to abort, type help(). for help)
[ME]: inside start()     <===========THIS
===> Booted cowlib
===> Booted ranch
===> Booted cowboy
===> Booted my_cowboy_server
1> 

Then I can open another terminal window and send a request to the cowboy server, for instance:

$ curl "http://localhost:8080/greet"
Hello Erlang!

If you want/need to use the template generating capabilities of Erlang.mk, for instance to create hello_handler.erl, you could follow the cowboy Getting Started guide and create the hello_erlang release, then any time you need a template switch into that directory, generate the template, copy it, modify the module name, etc., then move it into your rebar3 app.

The command rebar3 shell compiles and executes the same code in about 1 second compared to 30-40 seconds for Erlang.mk. Of course, rebar3 shell is not creating a release, but if you want to experiment with cowboy, then rebar3 seems like the best way to quickly compile and execute your code.

You can also make rebar3 recompile automatically by installing the rebar3 auto plugin.. To make this work, you also have to make some changes to your rebar.config file:

{plugins, [rebar3_run]}.

{deps,
 [{cowboy, "2.10.0"},
  ...
 ]
}.

{relx,
 [{release, {<myapp>, "git"},
   [ranch,
    cowboy,
    ...
   ]},
  ...
 ]
}.

%% auto-boot the app when calling `rebar3 shell or auto'
{shell, [ {app_reload_blacklist, [cowboy, ranch]}
	, {apps, [<myapp>]}
	]}.
{auto, [ {app_reload_blacklist, [cowboy, ranch]}
	, {apps, [<myapp>]}
	]}.

Add ranch and cowboy to relx to build working releases later on.
The app_reload_blacklist options in shell and auto are essential to make it work.

1 Like