Add Elixir support to ejabberd

This commit is contained in:
Mickaël Rémond 2015-01-29 18:43:47 +01:00 committed by Mickaël Rémond
parent cf929e730f
commit 01e1f677c7
11 changed files with 202 additions and 55 deletions

3
.gitignore vendored
View File

@ -35,3 +35,6 @@
/src/ejabberd.app.src
/src/eldap_filter_yecc.erl
/vars.config
/dialyzer/
/test/*.beam
/logs/

View File

@ -11,6 +11,9 @@ DESTDIR =
# /etc/ejabberd/
ETCDIR = $(DESTDIR)@sysconfdir@/ejabberd
# /bin/
BINDIR = $(DESTDIR)@bindir@
# /sbin/
SBINDIR = $(DESTDIR)@sbindir@
@ -118,6 +121,7 @@ install: all
|| $(INSTALL) -b -m 640 $(G_USER) ejabberd.yml.example $(ETCDIR)/ejabberd.yml
$(SED) -e "s*{{rootdir}}*@prefix@*" \
-e "s*{{installuser}}*@INSTALLUSER@*" \
-e "s*{{bindir}}*@bindir@*" \
-e "s*{{libdir}}*@libdir@*" \
-e "s*{{sysconfdir}}*@sysconfdir@*" \
-e "s*{{localstatedir}}*@localstatedir@*" \
@ -132,6 +136,11 @@ install: all
# Administration script
[ -d $(SBINDIR) ] || $(INSTALL) -d -m 755 $(SBINDIR)
$(INSTALL) -m 550 $(G_USER) ejabberdctl.example $(SBINDIR)/ejabberdctl
# Elixir binaries
[ -d $(BINDIR) ] || $(INSTALL) -d -m 755 $(BINDIR)
[ -f deps/elixir/bin/iex ] && $(INSTALL) -m 550 $(G_USER) deps/elixir/bin/iex $(BINDIR)/iex
[ -f deps/elixir/bin/elixir ] && $(INSTALL) -m 550 $(G_USER) deps/elixir/bin/elixir $(BINDIR)/elixir
[ -f deps/elixir/bin/mix ] && $(INSTALL) -m 550 $(G_USER) deps/elixir/bin/mix $(BINDIR)/mix
#
# Init script
$(SED) -e "s*@ctlscriptpath@*$(SBINDIR)*" \
@ -145,6 +154,9 @@ install: all
$(INSTALL) -m 644 ebin/*.beam $(BEAMDIR)
$(INSTALL) -m 644 deps/*/ebin/*.app $(BEAMDIR)
$(INSTALL) -m 644 deps/*/ebin/*.beam $(BEAMDIR)
# Install Elixir and Elixir dependancies
-$(INSTALL) -m 644 deps/*/lib/*/ebin/*.app $(BEAMDIR)
-$(INSTALL) -m 644 deps/*/lib/*/ebin/*.beam $(BEAMDIR)
rm -f $(BEAMDIR)/configure.beam
#
# ejabberd header files
@ -201,6 +213,9 @@ uninstall: uninstall-binary
uninstall-binary:
rm -f $(SBINDIR)/ejabberdctl
rm -f $(BINDIR)/iex
rm -f $(BINDIR)/elixir
rm -f $(BINDIR)/mix
rm -fr $(DOCDIR)
rm -f $(BEAMDIR)/*.beam
rm -f $(BEAMDIR)/*.app

View File

@ -106,10 +106,10 @@ AC_ARG_ENABLE(mssql,
esac],[db_type=generic])
AC_ARG_ENABLE(all,
[AC_HELP_STRING([--enable-all], [same as --enable-nif --enable-odbc --enable-mysql --enable-pgsql --enable-pam --enable-zlib --enable-riak --enable-json --enable-iconv --enable-debug --enable-lager --enable-tools (useful for Dialyzer checks, default: no)])],
[AC_HELP_STRING([--enable-all], [same as --enable-nif --enable-odbc --enable-mysql --enable-pgsql --enable-pam --enable-zlib --enable-riak --enable-json --enable-elixir --enable-iconv --enable-debug --enable-lager --enable-tools (useful for Dialyzer checks, default: no)])],
[case "${enableval}" in
yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true riak=true json=true iconv=true debug=true lager=true tools=true ;;
no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false riak=false json=false iconv=false debug=false lager=false tools=false ;;
yes) nif=true odbc=true mysql=true pgsql=true pam=true zlib=true riak=true json=true elixir=true iconv=true debug=true lager=true tools=true ;;
no) nif=false odbc=false mysql=false pgsql=false pam=false zlib=false riak=false json=false elixir=false iconv=false debug=false lager=false tools=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-all) ;;
esac],[])
@ -185,6 +185,14 @@ AC_ARG_ENABLE(json,
*) AC_MSG_ERROR(bad value ${enableval} for --enable-json) ;;
esac],[if test "x$json" = "x"; then json=false; fi])
AC_ARG_ENABLE(elixir,
[AC_HELP_STRING([--enable-elixir], [enable Elixir support (default: no)])],
[case "${enableval}" in
yes) elixir=true ;;
no) elixir=false ;;
*) AC_MSG_ERROR(bad value ${enableval} for --enable-elixir) ;;
esac],[if test "x$elixir" = "x"; then elixir=false; fi])
AC_ARG_ENABLE(iconv,
[AC_HELP_STRING([--enable-iconv], [enable iconv support (default: yes)])],
[case "${enableval}" in
@ -240,6 +248,7 @@ AC_SUBST(pam)
AC_SUBST(zlib)
AC_SUBST(riak)
AC_SUBST(json)
AC_SUBST(elixir)
AC_SUBST(iconv)
AC_SUBST(debug)
AC_SUBST(lager)

View File

@ -12,6 +12,7 @@ ERLANG_NODE=ejabberd@localhost
# define default environment variables
SCRIPT_DIR=`cd ${0%/*} && pwd`
ERL={{erl}}
IEX={{bindir}}/iex
INSTALLUSER={{installuser}}
# Compatibility in ZSH
@ -128,6 +129,7 @@ if [ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] ; then
else
NAME="-name"
fi
IEXNAME="-$NAME"
# define ejabberd environment parameters
if [ "$EJABBERD_CONFIG_PATH" != "${EJABBERD_CONFIG_PATH%.yml}" ] ; then
@ -182,6 +184,67 @@ start()
# attach to server
debug()
{
debugwarning
TTY=`tty | sed -e 's/.*\///g'`
$EXEC_CMD "$ERL \
$NAME debug-${TTY}-${ERLANG_NODE} \
-remsh $ERLANG_NODE \
-hidden \
$KERNEL_OPTS \
$ERLANG_OPTS $ARGS \"$@\""
}
# attach to server using Elixir
iexdebug()
{
debugwarning
TTY=`tty | sed -e 's/.*\///g'`
# Elixir shell is hidden as default
$EXEC_CMD "$IEX \
$IEXNAME debug-${TTY}-${ERLANG_NODE} \
--remsh $ERLANG_NODE \
--erl \"$KERNEL_OPTS\" \
--erl \"$ERLANG_OPTS\" --erl \"$ARGS\" --erl \"$@\""
}
# start interactive server
live()
{
livewarning
$EXEC_CMD "$ERL \
$NAME $ERLANG_NODE \
-pa $EJABBERD_EBIN_PATH \
-mnesia dir \"\\\"$SPOOL_DIR\\\"\" \
$KERNEL_OPTS \
$EJABBERD_OPTS \
-s ejabberd \
$ERLANG_OPTS $ARGS \"$@\""
}
# start interactive server with Elixir
iexlive()
{
livewarning
$EXEC_CMD "$IEX \
$IEXNAME $ERLANG_NODE \
-pa $EJABBERD_EBIN_PATH \
--erl \"-mnesia dir \\\"$SPOOL_DIR\\\"\" \
--erl \"$KERNEL_OPTS\" \
--erl \"$EJABBERD_OPTS\" \
--app ejabberd \
--erl \"$ERLANG_OPTS\" --erl $ARGS --erl \"$@\""
}
etop()
{
$EXEC_CMD "$ERL \
$NAME debug-${TTY}-${ERLANG_NODE} \
-hidden -s etop -s erlang halt -output text -node $ERLANG_NODE"
}
# TODO: refactor debug warning and livewarning
debugwarning()
{
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
echo "--------------------------------------------------------------------"
@ -199,21 +262,13 @@ debug()
echo "--------------------------------------------------------------------"
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press any key to continue"
echo "Press return to continue"
read foo
echo ""
fi
TTY=`tty | sed -e 's/.*\///g'`
$EXEC_CMD "$ERL \
$NAME debug-${TTY}-${ERLANG_NODE} \
-remsh $ERLANG_NODE \
-hidden \
$KERNEL_OPTS \
$ERLANG_OPTS $ARGS \"$@\""
}
# start interactive server
live()
livewarning()
{
check_start
if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then
@ -231,34 +286,22 @@ live()
echo "--------------------------------------------------------------------"
echo "To bypass permanently this warning, add to ejabberdctl.cfg the line:"
echo " EJABBERD_BYPASS_WARNINGS=true"
echo "Press any key to continue"
echo "Press return to continue"
read foo
echo ""
fi
$EXEC_CMD "$ERL \
$NAME $ERLANG_NODE \
-pa $EJABBERD_EBIN_PATH \
-mnesia dir \"\\\"$SPOOL_DIR\\\"\" \
$KERNEL_OPTS \
$EJABBERD_OPTS \
-s ejabberd \
$ERLANG_OPTS $ARGS \"$@\""
}
etop()
{
$EXEC_CMD "$ERL \
$NAME debug-${TTY}-${ERLANG_NODE} \
-hidden -s etop -s erlang halt -output text -node $ERLANG_NODE"
}
# TODO: Make iex command display only if ejabberd Elixir support has been enabled
help()
{
echo ""
echo "Commands to start an ejabberd node:"
echo " start Start an ejabberd node in server mode"
echo " debug Attach an interactive Erlang shell to a running ejabberd node"
echo " iexdebug Attach an interactive Elixir shell to a running ejabberd node"
echo " live Start an ejabberd node in live (interactive) mode"
echo " iexlive Start an ejabberd node in live (interactive) mode, within an Elixir shell"
echo ""
echo "Optional parameters when starting an ejabberd node:"
echo " --config-dir dir Config ejabberd: $ETC_DIR"
@ -409,7 +452,9 @@ wait_for_status()
case $ARGS in
' start') start;;
' debug') debug;;
' iexdebug') iexdebug;;
' live') live;;
' iexlive') iexlive;;
' etop') etop;;
' started') wait_for_status 0 30 2;; # wait 30x2s before timeout
' stopped') wait_for_status 3 15 2 && stop_epmd;; # wait 15x2s before timeout

13
lib/Ejabberd/hooks.ex Normal file
View File

@ -0,0 +1,13 @@
defmodule Ejabberd.Hooks do
# Generic hook setting features
def add(hook_name, host, module, function, priority) do
:ejabberd_hooks.add(hook_name, host, module, function, priority)
end
# Should be named 'removed'
def delete(hook_name, host, module, function, priority) do
:ejabberd_hooks.delete(hook_name, host, module, function, priority)
end
end

9
lib/Ejabberd/logger.ex Normal file
View File

@ -0,0 +1,9 @@
defmodule Ejabberd.Logger do
def critical(message, args \\ []), do: :lager.log(:critical, [], message, args)
def error(message, args \\ []), do: :lager.log(:error, [], message, args)
def warning(message, args \\ []), do: :lager.log(:warning, [], message, args)
def info(message, args \\ []), do: :lager.log(:info, [], message, args)
def debug(message, args \\ []), do: :lager.log(:debug, [], message, args)
end

2
lib/ejabber.ex Normal file
View File

@ -0,0 +1,2 @@
defmodule Ejabberd do
end

21
lib/mod_presence_demo.ex Normal file
View File

@ -0,0 +1,21 @@
defmodule ModPresenceDemo do
import Ejabberd.Logger # this allow using info, error, etc for logging
@behaviour :gen_mod
def start(host, _opts) do
info('Starting ejabberd module Presence Demo')
Ejabberd.Hooks.add(:set_presence_hook, host, __ENV__.module, :on_presence, 50)
:ok
end
def stop(host) do
info('Stopping ejabberd module Presence Demo')
Ejabberd.Hooks.delete(:set_presence_hook, host, __ENV__.module, :on_presence, 50)
:ok
end
def on_presence(user, _server, _resource, _packet) do
info('Receive presence for #{user}')
:none
end
end

View File

@ -95,6 +95,9 @@ CfgDeps = lists:flatmap(
{tag, "1.4.2"}}}];
({json, true}) ->
[{jiffy, ".*", {git, "git://github.com/davisp/jiffy"}}];
({elixir, true}) ->
[{rebar_elixir_plugin, ".*", {git, "git://github.com/yrashk/rebar_elixir_plugin"}},
{elixir, "1.1.*", {git, "git://github.com/elixir-lang/elixir"}}];
({iconv, true}) ->
[{p1_iconv, ".*", {git, "git://github.com/processone/eiconv"}}];
({lager, true}) ->
@ -142,6 +145,13 @@ CfgXrefs = lists:flatmap(
[]
end, Cfg),
ElixirConfig = case lists:keysearch(elixir, 1, Cfg) of
{value, {elixir, true}} ->
[{plugins, [rebar_elixir_compiler, rebar_exunit] },
{lib_dirs, ["deps/elixir/lib"]}];
_ ->
[]
end,
{ok, Cwd} = file:get_cwd(),
@ -157,7 +167,7 @@ Config = [{erl_opts, Macros ++ HiPE ++ DebugInfo ++
[{"(XC - UC) || (XU - X - B - "
++ string:join(CfgXrefs, " - ") ++ ")", []}]},
{post_hooks, PostHooks ++ CfgPostHooks},
{deps, Deps ++ CfgDeps}],
{deps, Deps ++ CfgDeps}] ++ ElixirConfig,
%%io:format("ejabberd configuration:~n ~p~n", [Config]),
Config.

View File

@ -707,26 +707,40 @@ replace_module(mod_roster_odbc) -> {mod_roster, odbc};
replace_module(mod_shared_roster_odbc) -> {mod_shared_roster, odbc};
replace_module(mod_vcard_odbc) -> {mod_vcard, odbc};
replace_module(mod_vcard_xupdate_odbc) -> {mod_vcard_xupdate, odbc};
replace_module(Module) -> Module.
replace_module(Module) ->
case is_elixir_module(Module) of
true -> expand_elixir_module(Module);
false -> Module
end.
replace_modules(Modules) ->
lists:map(
fun({Module, Opts}) ->
case replace_module(Module) of
{NewModule, DBType} ->
emit_deprecation_warning(Module, NewModule, DBType),
NewOpts = [{db_type, DBType} |
lists:keydelete(db_type, 1, Opts)],
{NewModule, transform_module_options(Module, NewOpts)};
NewModule ->
if Module /= NewModule ->
emit_deprecation_warning(Module, NewModule);
true ->
ok
end,
{NewModule, transform_module_options(Module, Opts)}
end
end, Modules).
replace_modules(Modules) -> lists:map( fun({Module, Opts}) -> case
replace_module(Module) of {NewModule, DBType} ->
emit_deprecation_warning(Module, NewModule, DBType), NewOpts =
[{db_type, DBType} | lists:keydelete(db_type, 1, Opts)],
{NewModule, transform_module_options(Module, NewOpts)}; NewModule
-> if Module /= NewModule -> emit_deprecation_warning(Module,
NewModule); true -> ok end, {NewModule,
transform_module_options(Module, Opts)} end end, Modules).
%% Elixir module naming
%% ====================
%% If module name start with uppercase letter, this is an Elixir module:
is_elixir_module(Module) ->
case atom_to_list(Module) of
[H|_] when H >= 65, H =< 90 -> true;
_ ->false
end.
%% We assume we know this is an elixir module
expand_elixir_module(Module) ->
case atom_to_list(Module) of
%% Module name already specified as an Elixir from Erlang module name
"Elixir." ++ _ -> Module;
%% if start with uppercase letter, this is an Elixir module: Append 'Elixir.' to module name.
ModuleString ->
list_to_atom("Elixir." ++ ModuleString)
end.
strings_to_binary([]) ->
[];
@ -1009,5 +1023,10 @@ emit_deprecation_warning(Module, NewModule, DBType) ->
" instead", [Module, NewModule, DBType]).
emit_deprecation_warning(Module, NewModule) ->
case is_elixir_module(NewModule) of
%% Do not emit deprecation warning for Elixir
true -> ok;
false ->
?WARNING_MSG("Module ~s is deprecated, use ~s instead",
[Module, NewModule]).
[Module, NewModule])
end.

View File

@ -26,6 +26,7 @@
{zlib, @zlib@}.
{riak, @riak@}.
{json, @json@}.
{elixir, @elixir@}.
{lager, @lager@}.
{iconv, @iconv@}.