diff --git a/.gitignore b/.gitignore index d5b7862f1..c72fcdc36 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ /src/ejabberd.app.src /src/eldap_filter_yecc.erl /vars.config +/dialyzer/ +/test/*.beam +/logs/ diff --git a/Makefile.in b/Makefile.in index 575b0bdc7..a9a16f04e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 diff --git a/configure.ac b/configure.ac index ddbc48b88..8715c86ba 100644 --- a/configure.ac +++ b/configure.ac @@ -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) diff --git a/ejabberdctl.template b/ejabberdctl.template index 4e8234c99..2025fd22d 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -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 \"$@\"" + fi } -# 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 " live Start an ejabberd node in live (interactive) mode" + 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 diff --git a/lib/Ejabberd/hooks.ex b/lib/Ejabberd/hooks.ex new file mode 100644 index 000000000..a9e0dfef7 --- /dev/null +++ b/lib/Ejabberd/hooks.ex @@ -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 diff --git a/lib/Ejabberd/logger.ex b/lib/Ejabberd/logger.ex new file mode 100644 index 000000000..bef1cb3aa --- /dev/null +++ b/lib/Ejabberd/logger.ex @@ -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 diff --git a/lib/ejabber.ex b/lib/ejabber.ex new file mode 100644 index 000000000..a843abc97 --- /dev/null +++ b/lib/ejabber.ex @@ -0,0 +1,2 @@ +defmodule Ejabberd do +end diff --git a/lib/mod_presence_demo.ex b/lib/mod_presence_demo.ex new file mode 100644 index 000000000..89fc60d87 --- /dev/null +++ b/lib/mod_presence_demo.ex @@ -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 diff --git a/rebar.config.script b/rebar.config.script index 30d479eab..64398f33e 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -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. diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index cacec8bf1..54a635905 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -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) -> - ?WARNING_MSG("Module ~s is deprecated, use ~s instead", - [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]) + end. diff --git a/vars.config.in b/vars.config.in index dce8cd204..7621d1390 100644 --- a/vars.config.in +++ b/vars.config.in @@ -26,6 +26,7 @@ {zlib, @zlib@}. {riak, @riak@}. {json, @json@}. +{elixir, @elixir@}. {lager, @lager@}. {iconv, @iconv@}.