From 28fe3bfd54390a4ea74156b5f182d59f69d32331 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Sun, 29 Nov 2009 16:44:30 -0700 Subject: [PATCH] More gross structural work; now has log levels and broken out log level control --- ebin/rebar.app | 6 +- include/rebar.hrl | 12 ++- priv/rebar | 137 +----------------------- src/rebar_app_installer.erl | 68 ++++++++++++ src/rebar_app_utils.erl | 21 +++- src/rebar_config.erl | 17 ++- src/rebar_core.erl | 208 ++++++++++++++++++++++++++++++++++++ src/rebar_erlc_compiler.erl | 13 ++- src/rebar_log.erl | 84 +++++++++++++++ 9 files changed, 418 insertions(+), 148 deletions(-) create mode 100644 src/rebar_app_installer.erl create mode 100644 src/rebar_core.erl create mode 100644 src/rebar_log.erl diff --git a/ebin/rebar.app b/ebin/rebar.app index f779b81..2127297 100644 --- a/ebin/rebar.app +++ b/ebin/rebar.app @@ -11,10 +11,14 @@ stdlib, sasl]}, {env, [ + %% Default log level + {log_level, error}, + %% Key/value list of base/default configuration used by %% rebar_config during initialization {default_config, [ - {app_modules, [ rebar_erlc_compiler ]} + {app_modules, [ rebar_erlc_compiler, + rebar_app_installer ]} ]} ]} ]}. diff --git a/include/rebar.hrl b/include/rebar.hrl index fba5885..35de9f5 100644 --- a/include/rebar.hrl +++ b/include/rebar.hrl @@ -1,8 +1,14 @@ -record(global_state, { working_dir }). --define(CONSOLE(Str, Args), io:format(Str, Args)). --define(WARN(Str, Args), io:format("WARN: " ++ Str, Args)). - -define(FAIL, throw({error, failed})). +-define(CONSOLE(Str, Args), io:format(Str, Args)). + +-define(DEBUG(Str, Args), rebar_log:log(debug, Str, Args)). +-define(INFO(Str, Args), rebar_log:log(info, Str, Args)). +-define(WARN(Str, Args), rebar_log:log(warn, Str, Args)). +-define(ERROR(Str, Args), rebar_log:log(error, Str, Args)). + +-define(FMT(Str, Args), lists:flatten(io_lib:format(Str, Args))). + diff --git a/priv/rebar b/priv/rebar index a9e229f..198f20d 100755 --- a/priv/rebar +++ b/priv/rebar @@ -28,138 +28,5 @@ -include_lib("rebar/include/rebar.hrl"). -main(Commands) -> - %% Pre-load the rebar app so that we get default configuration - application:load(rebar), - - %% From the current working directory, search recursively and find - %% all the application and release directories. We always terminate the - %% recursion at an application or release directory. - Cwd = rebar_utils:get_cwd(), - case target_type(Cwd) of - undefined -> - Targets = find_targets(Cwd); - {Type, Filename} -> - Targets = [{Type, Cwd, Filename}] - end, - - %% Prefix all the app targets to the code path so that inter-app compilation - %% works properly - update_code_path(Targets), - - %% Finally, apply the specified command to each target - apply_commands(Targets, Commands); -main(_) -> - io:format("usage: rebar ...\n"). - - -%% -%% Recursively find all the targets starting at a root directory -%% -find_targets(Root) -> - {ok, Files} = file:list_dir(Root), - find_targets(Files, Root, [], 1). - -find_targets([], _Root, Acc, _Depth) -> - Acc; -find_targets(_Files, _Root, Acc, 10) -> - Acc; -find_targets([F | Rest], Root, Acc, Depth) -> - AbsName = filename:join([Root, F]), - case target_type(AbsName) of - undefined -> - case filelib:is_dir(AbsName) of - true -> - {ok, SubFiles} = file:list_dir(AbsName), - Acc2 = find_targets(SubFiles, AbsName, Acc, Depth+1); - false -> - Acc2 = Acc - end; - {Type, Filename} -> - Acc2 = [{Type, AbsName, Filename} | Acc] - end, - find_targets(Rest, Root, Acc2, Depth). - -%% -%% Determine the target type of a given file: app, rel or undefined -%% -target_type(AbsName) -> - case rebar_app_utils:is_app_dir(AbsName) of - {true, AppFile} -> - {app, AppFile}; - false -> - case rebar_rel_utils:is_rel_dir(AbsName) of - true -> - {rel, ""}; - false -> - undefined - end - end. - - -%% -%% Given a command and target type, determine if the command is applicable -%% -valid_command(compile, app) -> true; -valid_command(clean, _Any) -> true; -valid_command(_, _) -> false. - - -%% -%% Add all application targets to the front of the code path -%% -update_code_path([]) -> - ok; -update_code_path([{app, Dir, _} | Rest]) -> - EbinDir = filename:join([Dir, "ebin"]), - true = code:add_patha(EbinDir), - update_code_path(Rest); -update_code_path([_ | Rest]) -> - update_code_path(Rest). - - -apply_commands(_Targets, []) -> - ok; -apply_commands(Targets, [CommandStr | Rest]) -> - %% Convert the command into an atom for convenience - Command = list_to_atom(CommandStr), - - %% Filter out all the targets, based on the specified command. - FilteredTargets = [{Type, Dir, Filename} || {Type, Dir, Filename} <- Targets, - valid_command(Command, Type) == true], - case apply_command(FilteredTargets, Command) of - ok -> - apply_commands(Targets, Rest); - Other -> - Other - end. - -apply_command([], _Command) -> - ok; -apply_command([{Type, Dir, File} | Rest], Command) -> - ok = file:set_cwd(Dir), - Config = rebar_config:new(Dir), - - %% Provide some info on where we are - ?CONSOLE("==> ~s (~s)\n", [filename:basename(Dir), Command]), - - %% Pull the list of modules that are associated with Type operations. Each module - %% will be inspected for a function matching Command and if found, will execute that. - Modules = rebar_config:get_modules(Config, Type), - case catch(run_modules(Modules, Command, Config, File)) of - ok -> - apply_command(Rest, Command); - Other -> - ?CONSOLE("~p failed while processing ~s: ~p", [Command, Dir, Other]) - end. - - -run_modules([], _Command, _Config, _File) -> - ok; -run_modules([Module | Rest], Command, Config, File) -> - case Module:Command(Config, File) of - ok -> - run_modules(Rest, Command, Config, File); - {error, Reason} -> - {error, Reason} - end. +main(Args) -> + rebar_core:run(Args). diff --git a/src/rebar_app_installer.erl b/src/rebar_app_installer.erl new file mode 100644 index 0000000..ae723c5 --- /dev/null +++ b/src/rebar_app_installer.erl @@ -0,0 +1,68 @@ +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_app_installer). + +-export([install/2]). + +-include("rebar.hrl"). + +%% =================================================================== +%% Public API +%% =================================================================== + +install(Config, File) -> + %% Load the app name and version from the .app file and construct + %% the app identifier + {ok, AppName, AppData} = rebar_app_utils:load_app_file(File), + Vsn = proplists:get_value(vsn, AppData), + AppId = ?FMT("~s-~s", [AppName, Vsn]), + ?CONSOLE("Installing: ~s\n", [AppId]), + + %% Check the erlang lib directory to see if this app identifier + %% is already present. + AppDir = filename:join([code:lib_dir(), AppId]), + case filelib:is_dir(AppDir) of + true -> + %% Already exists -- check for force=1 global flag and only + %% continue if it's set + case rebar_config:get_global(force, "0") of + "0" -> + ?ERROR("~s already exists. Installation failed."), + ?FAIL; + "1" -> + ?WARN("~s already exists, but forcibly overwriting."), + end; + false -> + ok + end, + + %% Wipe out any previous versions + ok = rebar_file_utils:rm_rf(Appdir), + + %% Re-create target + ok = rebar_file_utils:mkdir_p(AppDir), + + %% By default we copy the ebin, include, src and priv directories + diff --git a/src/rebar_app_utils.erl b/src/rebar_app_utils.erl index 24c8e1b..900fff0 100644 --- a/src/rebar_app_utils.erl +++ b/src/rebar_app_utils.erl @@ -24,7 +24,14 @@ %% ------------------------------------------------------------------- -module(rebar_app_utils). --export([is_app_dir/0, is_app_dir/1]). +-export([is_app_dir/0, is_app_dir/1, + load_app_file/1]). + +-include("rebar.hrl"). + +%% =================================================================== +%% Public API +%% =================================================================== is_app_dir() -> is_app_dir(rebar_util:get_cwd()). @@ -37,3 +44,15 @@ is_app_dir(Dir) -> _ -> false end. + +load_app_file(Filename) -> + case file:consult(Filename) of + {ok, [{application, AppName, AppData}]} -> + {ok, AppName, AppData}; + {error, Reason} -> + ?ERROR("Failed to load app file from ~s: ~p\n", [Filename, Reason]), + error; + Other -> + ?ERROR("Unexpected terms from app file ~s: ~p\n", [Filename, Other]), + error + end. diff --git a/src/rebar_config.erl b/src/rebar_config.erl index 0b3ffee..3e2087c 100644 --- a/src/rebar_config.erl +++ b/src/rebar_config.erl @@ -26,7 +26,8 @@ -export([new/1, get_modules/2, - get_list/3]). + get_list/3, + set_global/2, get_global/2]). -include("rebar.hrl"). @@ -72,7 +73,17 @@ get_list(Config, Key, Default) -> {ok, List} -> List end. - + +set_global(Key, Value) -> + application:set_env(rebar_global, Key, Value). + +get_global(Key, Default) -> + case application:get_env(rebar_global, Key) of + undefined -> + Default; + {ok, Value} -> + Value + end. %% =================================================================== @@ -82,6 +93,6 @@ get_list(Config, Key, Default) -> merge_terms([], Dict) -> Dict; merge_terms([{Key, Value} | Rest], Dict) -> - merge_terms(Rest, orddict:append(Key, Value, Dict)); + merge_terms(Rest, orddict:store(Key, Value, Dict)); merge_terms([_ | Rest], Dict) -> merge_terms(Rest, Dict). diff --git a/src/rebar_core.erl b/src/rebar_core.erl new file mode 100644 index 0000000..a18370e --- /dev/null +++ b/src/rebar_core.erl @@ -0,0 +1,208 @@ +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_core). + +-export([run/1]). + +%% =================================================================== +%% Public API +%% =================================================================== + +run(Args) -> + %% Filter all the flags (i.e. string of form key=value) from the + %% command line arguments. What's left will be the commands to run. + Commands = filter_flags(Args, []), + + %% Pre-load the rebar app so that we get default configuration + application:load(rebar), + + %% Initialize logging system + rebar_log:init(), + + %% From the current working directory, search recursively and find + %% all the application and release directories. We always terminate the + %% recursion at an application or release directory. + Cwd = rebar_utils:get_cwd(), + case target_type(Cwd) of + undefined -> + Targets = find_targets(Cwd); + {Type, Filename} -> + Targets = [{Type, Cwd, Filename}] + end, + + %% Prefix all the app targets to the code path so that inter-app compilation + %% works properly + update_code_path(Targets), + + %% Finally, apply the specified command to each target + apply_commands(Targets, Commands) + + +%% =================================================================== +%% Internal functions +%% =================================================================== + +%% +%% Seperate all commands (single-words) from flags (key=value) and store +%% values into the rebar_config global storage. +%% +filter_flags([], Commands) -> + lists:reverse(Commands); +filter_flags([Item | Rest], Commands) -> + case string:tokens(Item, "=") of + [Command] -> + filter_flags(Rest, [Command | Commands]); + [KeyStr, Value] -> + Key = list_to_atom(KeyStr), + rebar_config:set_global(Key, Value), + filter_flags(Rest, Commands); + Other -> + ?CONSOLE("Ignoring command line argument: ~p\n", [Other]), + filter_flags(Rest, Commands) + end. + + + +%% +%% Recursively find all the targets starting at a root directory +%% +find_targets(Root) -> + {ok, Files} = file:list_dir(Root), + find_targets(Files, Root, [], 1). + +find_targets([], _Root, Acc, _Depth) -> + Acc; +find_targets(_Files, _Root, Acc, 10) -> + Acc; +find_targets([F | Rest], Root, Acc, Depth) -> + AbsName = filename:join([Root, F]), + case target_type(AbsName) of + undefined -> + case filelib:is_dir(AbsName) of + true -> + {ok, SubFiles} = file:list_dir(AbsName), + Acc2 = find_targets(SubFiles, AbsName, Acc, Depth+1); + false -> + Acc2 = Acc + end; + {Type, Filename} -> + Acc2 = [{Type, AbsName, Filename} | Acc] + end, + find_targets(Rest, Root, Acc2, Depth). + +%% +%% Determine the target type of a given file: app, rel or undefined +%% +target_type(AbsName) -> + case rebar_app_utils:is_app_dir(AbsName) of + {true, AppFile} -> + {app, AppFile}; + false -> + case rebar_rel_utils:is_rel_dir(AbsName) of + true -> + {rel, ""}; + false -> + undefined + end + end. + + +%% +%% Given a command and target type, determine if the command is applicable +%% +valid_command(compile, app) -> true; +valid_command(install, app) -> true; +valid_command(clean, _Any) -> true; +valid_command(_, _) -> false. + + +%% +%% Add all application targets to the front of the code path +%% +update_code_path([]) -> + ok; +update_code_path([{app, Dir, _} | Rest]) -> + EbinDir = filename:join([Dir, "ebin"]), + true = code:add_patha(EbinDir), + update_code_path(Rest); +update_code_path([_ | Rest]) -> + update_code_path(Rest). + + +apply_commands(_Targets, []) -> + ok; +apply_commands(Targets, [CommandStr | Rest]) -> + %% Convert the command into an atom for convenience + Command = list_to_atom(CommandStr), + + %% Filter out all the targets, based on the specified command. + FilteredTargets = [{Type, Dir, Filename} || {Type, Dir, Filename} <- Targets, + valid_command(Command, Type) == true], + case apply_command(FilteredTargets, Command) of + ok -> + apply_commands(Targets, Rest); + Other -> + Other + end. + +apply_command([], _Command) -> + ok; +apply_command([{Type, Dir, File} | Rest], Command) -> + ok = file:set_cwd(Dir), + Config = rebar_config:new(Dir), + + %% Provide some info on where we are + ?CONSOLE("==> ~s (~s)\n", [filename:basename(Dir), Command]), + + %% Pull the list of modules that are associated with Type operations. Each module + %% will be inspected for a function matching Command and if found, will execute that. + Modules = rebar_config:get_modules(Config, Type), + case catch(run_modules(Modules, Command, Config, File)) of + ok -> + apply_command(Rest, Command); + Other -> + ?ERROR("~p failed while processing ~s: ~p", [Command, Dir, Other]) + end. + + +run_modules([], _Command, _Config, _File) -> + ok; +run_modules([Module | Rest], Command, Config, File) -> + case invoke_command(Module, Command, Config, File) of + ok -> + run_modules(Rest, Command, Config, File); + {error, Reason} -> + {error, Reason} + end. + +invoke_command(Module, Command, Config, File) -> + Exports = Module:module_info(exports), + case lists:member({Command, 2}, Exports) of + true -> + Module:Command(Config, File); + false -> + ?DEBUG("Skipping ~s:~s/2 (not exported)\n", [Module, Command]), + ok + end. diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl index 0218f77..ac1e733 100644 --- a/src/rebar_erlc_compiler.erl +++ b/src/rebar_erlc_compiler.erl @@ -66,7 +66,7 @@ do_compile(Config, SrcWildcard, OutDir, InExt, OutExt, CompileFn, FirstFiles) -> %% Build list of output files Targets = [target_file(S, OutDir, InExt, OutExt) || S <- Srcs], Files = lists:zip(Srcs, Targets), - + %% Make sure target directory exists ok = filelib:ensure_dir(hd(Targets)), @@ -87,6 +87,7 @@ compile_each([{Src, Target} | Rest], Config, CompileFn) -> ?CONSOLE("Compiling ~s\n", [Src]), CompileFn(Src, Config); false -> + ?INFO("Skipping ~s\n", [Src]), ok end, compile_each(Rest, Config, CompileFn). @@ -98,10 +99,12 @@ needs_compile(Src, Target) -> target_file(F, TargetDir, InExt, OutExt) -> filename:join([TargetDir, filename:basename(F, InExt) ++ OutExt]). +compile_opts(Config, Key) -> + rebar_config:get_list(Config, Key, []). compile_erl(Source, Config) -> - Opts = rebar_config:get_list(Config, erl_opts, []), - case compile:file(Source, [{i, "include"}, {outdir, "ebin"}, report] ++ Opts) of + Opts = [{i, "include"}, {outdir, "ebin"}, report] ++ compile_opts(Config, erl_opts), + case compile:file(Source, Opts) of {ok, _} -> ok; error -> @@ -109,8 +112,8 @@ compile_erl(Source, Config) -> end. compile_mib(Source, Config) -> - Opts = rebar_config:get_list(Config, mib_opts, []), - case snmpc:compile(Source, [{outdir, "priv/mibs"}, {i, ["priv/mibs"]}] ++ Opts) of + Opts = [{outdir, "priv/mibs"}, {i, ["priv/mibs"]}] ++ compile_opts(Config, mib_opts), + case snmpc:compile(Source, Opts) of {ok, _} -> ok; {error, compilation_failed} -> diff --git a/src/rebar_log.erl b/src/rebar_log.erl new file mode 100644 index 0000000..58f7992 --- /dev/null +++ b/src/rebar_log.erl @@ -0,0 +1,84 @@ +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_log). + +-export([init/0, + set_level/1, get_level/0, + log/3]). + +%% =================================================================== +%% Public API +%% =================================================================== + +init() -> + case rebar_config:get_global(verbose, "0") of + "1" -> + set_level(debug); + _ -> + set_level(error) + end. + + +set_level(Level) -> + ok = application:set_env(rebar, log_level, Level). + +get_level() -> + case application:get_env(rebar, log_level) of + undefined -> + error; + {ok, Value} -> + Value + end. + +log(Level, Str, Args) -> + {ok, LogLevel} = application:get_env(rebar, log_level), + case should_log(LogLevel, Level) of + true -> + io:format(log_prefix(Level) ++ Str, Args); + false -> + ok + end. + +%% =================================================================== +%% Internal functions +%% =================================================================== + +should_log(debug, _) -> true; +should_log(info, debug) -> false; +should_log(info, _) -> true; +should_log(warn, debug) -> false; +should_log(warn, info) -> false; +should_log(warn, _) -> true; +should_log(error, error) -> true; +should_log(error, _) -> false; +should_log(_, _) -> false. + +log_prefix(debug) -> "DEBUG:" ; +log_prefix(info) -> "INFO: "; +log_prefix(warn) -> "WARN: "; +log_prefix(error) -> "ERROR: ". + + +