Erlang: How to plug Elvis style check to rebar3 project properly? - erlang

I'm adding Elvis check style to Erlang project built with rebar3.
Initially I've found rebar3_lint plugin for this. But then it appeared to use some old version of Elvis and fails not very graciously on errors.
Then after some play I attached elvis directly as a test dependency and then calling it with pre_hook and erlang code line:
{profiles, [
{test, [
{deps, [
{elvis_core,
{git, "https://github.com/inaka/elvis_core",
{branch, "master"}}}
]},
{pre_hooks, [{compile,
"erl -pa _build/test/lib/*/ebin -noshell -eval 'init:stop(case elvis_core:rock() of ok -> 0; _ -> 1 end)'"}]}
]},
]}
This works nicely, though looks awkward.
So is there any more "proper" way? Thanks in advance!

Related

Command 'escriptize not understood or not applicable

When I run ./rebar get-deps compile escriptize, it fails with the following:
WARN: 'escriptize' command does not apply to directory /path/to/foo
Command 'escriptize' not understood or not applicable
What have I forgotten?
Even though you're generating an escript package, rebar still needs the foo.app file. If you didn't use ./rebar create-app appid=foo to create your "application", you'll need to create one by hand:
{application, foo,
[
{description, ""},
{vsn, "1"},
{registered, []},
{applications, [
kernel, stdlib
]},
{env, []}
]}.
Note that you might need to ./rebar compile again in order to generate the foo.app file before ./rebar escriptize will generate the script correctly.

Can't send anything to spawned Erlang process

I have the following Erlang code:
#!/usr/bin/env escript
%%! -pz ../deps/amqp_client ../deps/rabbit_common ../deps/amqp_client/ebin ../deps/rabbit_common/ebin
% RMQ module
-module(rmq).
-export([main/1, send/1, validate/0, test/0]).
-include_lib("../deps/amqp_client/include/amqp_client.hrl").
main(_) ->
%send(<<"test_esio">>),
%validate(),
Pid = spawn(rmq, test, []),
% Pid = spawn(fun() -> test() end), <= I've tried this way too
Pid ! s.
test() ->
receive
s ->
io:format("BAR ~n"),
send(<<"esio">>),
test();
get ->
validate(),
test();
_ ->
io:format("FOO"),
test()
end.
I run this with:
excript rmq.erl
This code doesn't work. Looks like spawn doesn't work.
Rest of my code works, function send and validate works correctly if I run it from main (I've commented its). What I'm doing wrong?
Sorry, maybe it's a dumb question but I'm a beginner with erlang. I've tried search answer in internet and books and I failed...
The problem is not actually in spawn, but in module/escript confusion.
In few words, escript file are not really modules, not from point of Erlang VM, even if you use -module() directive. They are interpreted, and not compiled at all, and definitely they can not be called by module like "rmq:test()", or in you case trough dynamic module call by spawn.
Easiest solution is separate script from actual modules. In your rmq.es you would just start some proper module:
#!/usr/bin/env escript
%%! -pz ../deps/amqp_client ../deps/rabbit_common ../deps/amqp_client/ebin ../deps/rabbit_common/ebin
main(_) ->
rmq:start().
And there in module rmq.erl:
-module(rmq).
-export([start/0, send/1, validate/0, test/0]).
-include_lib("../deps/amqp_client/include/amqp_client.hrl").
start() ->
Pid = spawn(rmq, test, []),
%% Pid = spawn(?MODULE, test, []), %% works with macro too
%% Pid = spawn(fun() -> test() end), <= I've tried this way too
Pid ! s.
test() ->
receive
s ->
io:format("BAR ~n"),
send(<<"esio">>),
test();
get ->
validate(),
test();
_ ->
io:format("FOO"),
test()
end.
Or you could just start this module without escript, with -run flag like this
erl -pz deps/*/ebin -run rmq start
EDIT regarding compilation problems
You compile your modules with erlc command. To just compile use erlc rmq.erl, which will produce rmq.beam file in current directory. But convention is to keep all your source files in src directory, all compiled files in ebin direcory, and things like run-scripts could be placed in the top directory. Something like that:
project
|-- ebin
| |-- rmq.beam
|
|-- src
| |-- rmq.erl
|
|-- rmq.es
Assuming that you run all your shell commands form project directory, to compile all file from src and place .beam binaries in ebin use erlc src/rmq.erl -o ebin, or erlc src/* -o ebin In documentation you can find you explanation of -o flag"
-o directory
The directory where the compiler should place the output files. If not specified, output files will be placed in the current working directory.
Then, after compilation you can run your code, either with erl Erlang VM or using escript (which kind-off uses erl.
erl to runs code from compiled modules, and to do that he needs to be able to locate those compiled *.ebin binaries. For this he uses code path, which is the list of directors in which he will search for those files. This list automatically consist standard library directories, and of course you can add to it directories with your own code with use of -pa flag.
-pa Dir1 Dir2 ...
Adds the specified directories to the beginning of the code path, similar to code:add_pathsa/1. See code(3). As an alternative to -pa, if several directories are to be prepended to the code and the directories have a common parent directory, that parent directory could be specified in the ERL_LIBS environment variable. See code(3).
In your case it would be erl -pa ebin, or to include binaries of all deps you can use erl -pa ebin -pa deps/*/ebin.
Exactly same options are used in second line of your escript. With exception of * character, which will not be expand like it would be in the shell. In escript you have to provide paths to each dependency separately. But the idea of including -pa ebin stays exactly the same.
To automate and standardize this process tools like rebar and erlang.mk where created (I would recommend the later). Using those should help you a little with your workflow.

Compiling LFE files with make

Is there a standard way of compiling .lfe source files from a make rule in an OTP project?
According to the docs, I'm supposed to use lfe_comp:file/1, which doesn't help much if I want to compile multiple such files in an OTP application (where I'm supposed to keep the source files in src, but the binaries in ebin).
Ideally, I'd be able to do something like
erlc -Wf -o ebin src/*lfe
But there doesn't seem to be lfe support in erlc. The best solution I can think of off the top of my head is
find src/*lfe -exec erl -s lfe_comp file {} -s init stop \;
mv src/*beam ebin/
but that seems inelegant. Any better ideas?
On suggestion from rvirding, here's a first stab at lfec that does what I want above (and pretty much nothing else). I'd invoke it from a Makefile with ./lfec -o ebin src/*lfe.
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname lfec -mnesia debug verbose
main(Arguments) ->
try
{Opts, Args} = parse_opts(Arguments),
case get_opt("-o", Opts) of
false ->
lists:map(fun lfe_comp:file/1, Args);
Path ->
lists:map(fun (Arg) -> lfe_comp:file(Arg, [{outdir, Path}]) end,
Args)
end
catch
_:_ -> usage()
end;
main(_) -> usage().
get_opt(Target, Opts) ->
case lists:keyfind(Target, 1, Opts) of
false -> false;
{_} -> true;
{_, Setting} -> Setting
end.
parse_opts(Args) -> parse_opts(Args, []).
parse_opts(["-o", TargetDir | Rest], Opts) ->
parse_opts(Rest, [{"-o", TargetDir} | Opts]);
parse_opts(Args, Opts) -> {Opts, Args}.
usage() ->
io:format("usage:\n"),
io:format("-o [TargetDir] -- output files to specified directory\n"),
halt(1).
Not really. LFE is not supported by OTP so erlc does not know about .lfe files. And as far as I know there is no way to "open up" erlc and dynamically add information how to process files. An alternative would be to write an lfec script for this. I will think about it.
Just as a matter of interest what are using LFE for?

Starting application with Erlang

I would like to write a standalone Erlang application which could be run just like any other program (by clicking on .exe executable). Something like Wings3D does.
How can I do it ? What is the best approach to make it possible ? I am on Windows platform, if it is important.
2nd UPDATE:
Well, I haven't exported the promodb:start/0 function and that was the reason Erlang could'n start (and, as the Muzaaya Joshua says, werl -s does not work). But now, I have another strange problem. In my rebar.config I have 2 dependencies:
{deps, [
{cowboy, ".*", {git, "https://github.com/extend/cowboy.git"}},
{erlydtl, ".*", {git, "https://github.com/evanmiller/erlydtl.git"}}
]
}.
and after I start Erlang using
erl -pa ebin deps/cowboy/ebin deps/erlydtl/ebin deps/proper/ebin -s promodb
I find by application:which_application() that only Cowboy is started:
[{cowboy,"Small, fast, modular HTTP server.","0.4.0"},
{stdlib,"ERTS CXC 138 10","1.18"},
{kernel,"ERTS CXC 138 10","2.15"}]
As far as I know, Erlang should start all needed applications that I put in my promodb.app file, as:
{application, promodb,
[
{description, ""},
{vsn, "0.1"},
{registered, [promodb_sup]},
{applications, [
kernel,
stdlib,
cowboy,
erlydtl
]},
{mod, { promodb, []}},
{env, []}
]}.
What did I do wrong ?
I use rebar escriptize to pack a standalone package like rebar. Then you can use ./PackageName in linux or escript.exe PackageName in windows.
I think you need to create a release package and a boot script as explained in the documentation.
Actually the -s option works everywhere. Just use erl NOT werl. The werl command attempts to load a GUI on Windows.
The BitRock Install Builder is a great tool you need to check out. Yaws Web Server written in Erlang, uses it and many other applications.
Erlydtl doesn't have an application behaviour, it works like a library. As you can see in erlydtl examples, no app is needed to be started.
https://github.com/evanmiller/erlydtl#template-compilation
Check this:
erl -pa ebin deps/*/ebin
1> erlydtl:compile(<<"{{ foo }}">>, my_module_name).
Does it works?
I like escriptize rebar option to create executables.

Starting inets/httpd with custom application

I've got a module that I'm attempting to turn into a proper OTP application. Currently, the module has start/0 which starts a genserver which supplies configuration data read from a config file. It then calls inets:start(httpd,config:lookup(httpd_conf)). I gather that I need to move the starting of these out into the .app file's (application list) but I'm not sure how to get my config data into the inets:start function (or pass in httpd)?
Thanks,
--tim
Here's what I've figured out...
First, I needed to create the inets config file:
inets.config:
[{inets, [{services, [{httpd, [{proplist_file,
"8080.conf"}]},
].
Then, create the httpd conf file:
8080.conf
[
{modules, [
mod_alias,
mod_auth,
mod_esi,
mod_actions,
mod_cgi,
mod_dir,
mod_get,
mod_head,
mod_log,
mod_disk_log
]},
{port,8080},
{server_name,"hello_world"},
{server_root,"log"},
{document_root,"www"},
{erl_script_alias, {"/erl", [hello_world]}},
{error_log, "error.log"},
{security_log, "security.log"},
{transfer_log, "transfer.log"},
{mime_types,[
{"html","text/html"},
{"css","text/css"},
{"js","application/x-javascript"}
]}
]
Now, when booting my app, I just reference the inets.conf file with:
$ erl -boot start_sasl -pa ebin -config inets.config
This seems to work not sure if it's the "right" way or not...

Resources