From Rails to Erlyweb - Part II Manage Project - Reloaded
The migrating from Rails to Erlyweb of our project is going to be finished. I got more experience on how to deal with Erlyweb. First, the project management can be more straightforward. Here is it:
2. Manage project - Reloaded
Erlyweb provides erlyweb:compile(App, ..) to compile the source files under app directory. To start an app, you usually should erlydb:start(mysql, ....) and compile app files first. To make life easy, you can put some scripting like code under myproject\script directory. Here's my project source tree:
myproject
+ apps
| + myapp
| + ebin
| + include
| + nbproject
| + src
| + components
| + lib
| + services
| + test
| + www
+ config
| * yaws.conf
| * erlyweb.conf
+ script
+ ebin
+ src
* erlyweb_app.erl
Where, config/yaws.conf contains the yaws' configuration. Here's mine:
ebin_dir = D:/myapp/trunk/script/ebin
<server localhost>
port = 8000
listen = 0.0.0.0
docroot = D:/myapp/trunk/apps/myapp/www
appmods = </myapp, erlyweb>
start_mod = erlyweb_app
<opaque>
appname = myapp
environment = development
</opaque>
</server>
You may have noticed, all beams under D:/myapp/trunk/script/ebin will be auto-loaded when yaws starts up. And you can prepare another yaws.conf for test or production environment by change the environment var in opaque
Now the config/erlyweb.conf:
{pa, ["script/ebin",
"apps/myapp/ebin",
"vendor/erlyweb/ebin",
"vendor/eunit/ebin"]}.
{i, ["vendor",
"apps/myapp/include",
"/usr/local/lib/yaws"]}.
{production, [{dbdriver, mysql},
{database, "mydb_production"},
{hostname, "localhost"},
{username, "mememe"},
{password, "pwpwpw"}]}.
{development, [{dbdriver, mysql},
{database, "mydb_development"},
{hostname, "localhost"},
{username, "mememe"},
{password, "pwpwpw"}]}.
{test, [{dbdriver, mysql},
{database, "mydb_test"},
{hostname, "localhost"},
{username, "mememe"},
{password, "pwpwpw"}]}.
erlyweb_app.erl is the boot scripting code, which will be used to start db connection and compile the code. Currently I run these scripts manually. I'll talk later.
Notice: erlyweb 0.6.2 needed, which contains Haoboy's logfun patch.
%% @doc Main entrance to the entire erlyweb application.
-module(erlyweb_app).
-export([start/1]).
-export([get_conf/1,
build/1,
build_test/1,
build_product/1,
environment/1,
decompile/2,
db_log/4,
db_dummy_log/4
]).
-include("yaws/include/yaws.hrl").
-include("yaws/include/yaws_api.hrl").
db_log(Module, Line, Level, FormatFun) ->
mysql:log(Module, Line, Level, FormatFun).
db_dummy_log(_Mod, _Line, _Level, _FormatFun) ->
empty.
%% @doc call back function when yaws start an app
%% @see man yaws.conf
%% start_mod = Module
%% Defines a user provided callback module. At startup of the
%% server, Module:start/1 will be called. The #sconf{} record
%% (defined in yaws.hrl) will be used as the input argument. This
%% makes it possible for a user application to syncronize the
%% startup with the yaws server as well as getting hold of user
%% specific configuration data, see the explanation for the
%% context.
start(SConf) ->
Opaque = SConf#sconf.opaque,
AppName = proplists:get_value("appname", Opaque),
Environment = list_to_atom(proplists:get_value("environment", Opaque)),
{_I, Pa, Pz, Dbdriver, Database, Hostname, Username, Password} = get_conf(Environment),
{ok, Cwd} = file:get_cwd(),
error_logger:info_msg("CWD: ~s~n", [Cwd]),
add_code_path(Pa, Pz),
LogFun =
case Environment of
undefined ->
fun erlyweb_app:db_log/4;
production ->
fun erlyweb_app:db_dummy_log/4;
development ->
%code:add_pathz("../apps/ewp/src/test"),
fun erlyweb_app:db_log/4;
test ->
fun erlyweb_app:db_log/4
end,
error_logger:info_msg("Starting app <~s> as <~s> using database <~s>~n",
[AppName, Environment, Database]),
start_db(Dbdriver, Database, Hostname, Username, Password, LogFun).
add_code_path(Pa, Pz) ->
AddedPa = [{Dir, code:add_patha(Dir)} || Dir <- Pa],
AddedPz = [{Dir, code:add_pathz(Dir)} || Dir <- Pz],
error_logger:info_msg("Add code patha: ~p~n", [AddedPa]),
error_logger:info_msg("Add code pathz: ~p~n", [AddedPz]).
get_conf(Environment) when is_list(Environment) ->
get_conf(list_to_atom(Environment));
get_conf(Environment) when is_atom(Environment) ->
{ok, Confs} = file:consult("config/erlyweb.conf"),
I =
case proplists:get_value(i, Confs) of
undefined -> [];
IX -> IX
end,
Pa =
case proplists:get_value(pa, Confs) of
undefined -> [];
PaX -> PaX
end,
Pz =
case proplists:get_value(pz, Confs) of
undefined -> [];
PzX -> PzX
end,
EnvConfs = proplists:get_value(Environment, Confs),
Dbdriver = proplists:get_value(dbdriver, EnvConfs),
Database = proplists:get_value(database, EnvConfs),
Hostname = proplists:get_value(hostname, EnvConfs),
Username = proplists:get_value(username, EnvConfs),
Password = proplists:get_value(password, EnvConfs),
{I, Pa, Pz, Dbdriver, Database, Hostname, Username, Password}.
start_db(Dbdriver, Database, Hostname, Username, Password, LogFun) ->
erlydb:start(Dbdriver, [{database, Database},
{hostname, Hostname},
{username, Username},
{password, Password},
{logfun, LogFun}]).
%% This is developer's entrance to the module.
build(AppName) ->
io:format("Building development version of ~s.~n", [AppName]),
build(AppName, [debug_info], development).
build_test(AppName) ->
io:format("Building test version of ~s.~n", [AppName]),
build(AppName, [debug_info], test).
build_product(AppName) ->
io:format("Building product version of ~s.~n", [AppName]),
build(AppName, [no_debug_info], production).
build(AppName, Options, Environment) when is_atom(AppName) ->
build(atom_to_list(AppName), Options, Environment);
build(AppName, Options, Environment) when is_list(AppName) ->
{I, Pa, Pz, Dbdriver, Database, Hostname, Username, Password} = get_conf(Environment),
add_code_path(Pa, Pz),
start_db(Dbdriver, Database, Hostname, Username, Password, fun erlyweb_app:db_log/4),
compile(AppName, Options ++ [{auto_compile, false}], I, Dbdriver).
compile(AppName, Options, I, Dbdriver) ->
erlyweb:compile("./apps/" ++ AppName, lists:foldl(
fun(Dir, Acc) ->
[{i, filename:absname(Dir)} | Acc]
end, [], I) ++
[{erlydb_driver, Dbdriver}] ++ Options).
decompile(AppName, Beam) when is_list(AppName) ->
decompile(list_to_atom(AppName), Beam);
decompile(AppName, Beam) when is_atom(AppName) ->
{BinFilename, SrcFilename} =
case AppName of
erlyweb ->
{"./vendor/erlyweb/ebin/" ++ atom_to_list(Beam),
"./erlyweb_" ++ atom_to_list(Beam)};
_ ->
{"./apps/" ++ atom_to_list(AppName) ++ "/ebin/" ++ atom_to_list(Beam),
"./apps/" ++ atom_to_list(AppName) ++ "_" ++ atom_to_list(Beam)}
end,
decompile_beam(BinFilename, SrcFilename).
decompile_beam(BinFilename, SrcFilename) ->
io:format("Beam file: ~s~n", [BinFilename]),
io:format("Source file: ~s~n", [SrcFilename++".erl"]),
{ok, {_, [{abstract_code, {_, AC}}]}} = beam_lib:chunks(BinFilename, [abstract_code]),
%% do not with ".erl" ext?, otherwise will be compiled by erlyweb
{ok, S} = file:open(SrcFilename ++ ".erl", write),
io:fwrite(S, "~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).
To build it,
> erlc -I /opt/local/lib/yaws/include erlyweb_app.erl -o ebin
The erlyweb_app.erl is almost escript ready, but I use it as module functions currently. It's pre-compiled and erlyweb_app.beam is placed under script/ebin
So, I start myapp by steps:
cd \myproject yaws -sname myapp -i --conf config/yaws.conf --erlang "-smp auto" 1> erlyweb_app:build(myapp).
The erlyweb_app.erl is almost escript ready, but I use it as module functions currently. It's pre-compiled and erlyweb_app.beam is placed under script/ebin
After I made changes to myapp, I run above erlyweb_app:build(myapp). again, then everything is up to date.
And if you'd like to build it from another erl shell, try this:
erl -sname erlybird (erlybird@myhost)1> rpc:call(myapp@myhost, erlyweb_app, build, [myapp])
Yes, next version of ErlyBird will support building erlyweb apps remotely in ErlyBird's Erlang shell.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/blog_logo.png)
rss
Comments
No comments.