You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

305 lines
10 KiB

пре 15 година
пре 15 година
пре 15 година
пре 15 година
пре 15 година
пре 15 година
  1. %% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. %% -------------------------------------------------------------------
  4. %%
  5. %% rebar: Erlang Build Tools
  6. %%
  7. %% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
  8. %%
  9. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  10. %% of this software and associated documentation files (the "Software"), to deal
  11. %% in the Software without restriction, including without limitation the rights
  12. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. %% copies of the Software, and to permit persons to whom the Software is
  14. %% furnished to do so, subject to the following conditions:
  15. %%
  16. %% The above copyright notice and this permission notice shall be included in
  17. %% all copies or substantial portions of the Software.
  18. %%
  19. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. %% THE SOFTWARE.
  26. %% -------------------------------------------------------------------
  27. -module(rebar_core).
  28. -export([run/1]).
  29. -export([app_dir/1, rel_dir/1]). % Ugh
  30. -include("rebar.hrl").
  31. -ifndef(BUILD_TIME).
  32. -define(BUILD_TIME, "undefined").
  33. -endif.
  34. %% ===================================================================
  35. %% Public API
  36. %% ===================================================================
  37. run(["version"]) ->
  38. %% Load application spec and display vsn and build time info
  39. ok = application:load(rebar),
  40. {ok, Vsn} = application:get_key(rebar, vsn),
  41. ?CONSOLE("Version ~s built ~s\n", [Vsn, ?BUILD_TIME]),
  42. ok;
  43. run(RawArgs) ->
  44. %% Pre-load the rebar app so that we get default configuration
  45. ok = application:load(rebar),
  46. %% Parse out command line arguments -- what's left is a list of commands to
  47. %% run
  48. Commands = parse_args(RawArgs),
  49. %% Make sure crypto is running
  50. crypto:start(),
  51. %% Initialize logging system
  52. rebar_log:init(),
  53. %% Convert command strings to atoms
  54. CommandAtoms = [list_to_atom(C) || C <- Commands],
  55. %% Load rebar.config, if it exists
  56. process_dir(rebar_utils:get_cwd(), rebar_config:new(), CommandAtoms).
  57. %% ===================================================================
  58. %% Internal functions
  59. %% ===================================================================
  60. %%
  61. %% Parse command line arguments using getopt and also filtering out any
  62. %% key=value pairs. What's left is the list of commands to run
  63. %%
  64. parse_args(Args) ->
  65. %% Parse getopt options
  66. OptSpecList = option_spec_list(),
  67. case getopt:parse(OptSpecList, Args) of
  68. {ok, {_Options, []}} ->
  69. %% no command to run specified
  70. getopt:usage(OptSpecList, "rebar"),
  71. halt(1);
  72. {ok, {Options, NonOptArgs}} ->
  73. case proplists:get_bool(help, Options) of
  74. true ->
  75. %% display help
  76. getopt:usage(OptSpecList, "rebar"),
  77. halt(0);
  78. false ->
  79. %% Set global variables based on getopt options
  80. set_global_flag(Options, verbose),
  81. set_global_flag(Options, force),
  82. %% Filter all the flags (i.e. strings of form key=value) from the
  83. %% command line arguments. What's left will be the commands to run.
  84. filter_flags(NonOptArgs, [])
  85. end;
  86. {error, {Reason, Data}} ->
  87. ?ERROR("Error: ~s ~p~n~n", [Reason, Data]),
  88. getopt:usage(OptSpecList, "rebar"),
  89. halt(1)
  90. end.
  91. %%
  92. %% set global flag based on getopt option boolean value
  93. %%
  94. set_global_flag(Options, Flag) ->
  95. Value = case proplists:get_bool(Flag, Options) of
  96. true ->
  97. "1";
  98. false ->
  99. "0"
  100. end,
  101. rebar_config:set_global(Flag, Value).
  102. %%
  103. %% options accepted via getopt
  104. %%
  105. option_spec_list() ->
  106. [
  107. %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg}
  108. {help, $h, "help", undefined, "Show the program options"},
  109. {verbose, $v, "verbose", undefined, "Be verbose about what gets done"},
  110. {force, $f, "force", undefined, "Force"}
  111. ].
  112. %%
  113. %% Seperate all commands (single-words) from flags (key=value) and store
  114. %% values into the rebar_config global storage.
  115. %%
  116. filter_flags([], Commands) ->
  117. lists:reverse(Commands);
  118. filter_flags([Item | Rest], Commands) ->
  119. case string:tokens(Item, "=") of
  120. [Command] ->
  121. filter_flags(Rest, [Command | Commands]);
  122. [KeyStr, Value] ->
  123. Key = list_to_atom(KeyStr),
  124. rebar_config:set_global(Key, Value),
  125. filter_flags(Rest, Commands);
  126. Other ->
  127. ?CONSOLE("Ignoring command line argument: ~p\n", [Other]),
  128. filter_flags(Rest, Commands)
  129. end.
  130. process_dir(Dir, ParentConfig, Commands) ->
  131. ok = file:set_cwd(Dir),
  132. Config = rebar_config:new(ParentConfig),
  133. %% Save the current code path and then update it with
  134. %% lib_dirs. Children inherit parents code path, but we
  135. %% also want to ensure that we restore everything to pristine
  136. %% condition after processing this child
  137. CurrentCodePath = update_code_path(Config),
  138. %% Get the list of processing modules and check each one against
  139. %% CWD to see if it's a fit -- if it is, use that set of modules
  140. %% to process this dir.
  141. {ok, AvailModuleSets} = application:get_env(rebar, modules),
  142. {DirModules, ModuleSetFile} = choose_module_set(AvailModuleSets, Dir),
  143. %% Get the list of modules for "any dir". This is a catch-all list of modules
  144. %% that are processed in addion to modules associated with this directory
  145. %5 type. These any_dir modules are processed FIRST.
  146. {ok, AnyDirModules} = application:get_env(rebar, any_dir_modules),
  147. Modules = AnyDirModules ++ DirModules,
  148. %% Give the modules a chance to tweak config and indicate if there
  149. %% are any other dirs that might need processing first.
  150. {UpdatedConfig, Dirs} = acc_modules(select_modules(Modules, preprocess, []),
  151. preprocess, Config, ModuleSetFile, []),
  152. ?DEBUG("~s subdirs: ~p\n", [Dir, Dirs]),
  153. [process_dir(D, UpdatedConfig, Commands) || D <- Dirs],
  154. %% Make sure the CWD is reset properly; processing subdirs may have caused it
  155. %% to change
  156. ok = file:set_cwd(Dir),
  157. %% Finally, process the current working directory
  158. apply_commands(Commands, Modules, UpdatedConfig, ModuleSetFile),
  159. %% Once we're all done processing, reset the code path to whatever
  160. %% the parent initialized it to
  161. restore_code_path(CurrentCodePath),
  162. ok.
  163. %%
  164. %% Given a list of module sets from rebar.app and a directory, find
  165. %% the appropriate subset of modules for this directory
  166. %%
  167. choose_module_set([], _Dir) ->
  168. {[], undefined};
  169. choose_module_set([{Fn, Modules} | Rest], Dir) ->
  170. case ?MODULE:Fn(Dir) of
  171. {true, File} ->
  172. {Modules, File};
  173. false ->
  174. choose_module_set(Rest, Dir)
  175. end.
  176. %%
  177. %% Return .app file if the current directory is an OTP app
  178. %%
  179. app_dir(Dir) ->
  180. rebar_app_utils:is_app_dir(Dir).
  181. %%
  182. %% Return the reltool.config file if the current directory is release directory
  183. %%
  184. rel_dir(Dir) ->
  185. rebar_rel_utils:is_rel_dir(Dir).
  186. apply_commands([], _Modules, _Config, _ModuleFile) ->
  187. ok;
  188. apply_commands([Command | Rest], Modules, Config, ModuleFile) ->
  189. case select_modules(Modules, Command, []) of
  190. [] ->
  191. apply_commands(Rest, Modules, Config, ModuleFile);
  192. TargetModules ->
  193. %% Provide some info on where we are
  194. Dir = rebar_utils:get_cwd(),
  195. ?CONSOLE("==> ~s (~s)\n", [filename:basename(Dir), Command]),
  196. %% Run the available modules
  197. case catch(run_modules(TargetModules, Command, Config, ModuleFile)) of
  198. ok ->
  199. apply_commands(Rest, Modules, Config, ModuleFile);
  200. {error, failed} ->
  201. ?FAIL;
  202. Other ->
  203. ?ERROR("~p failed while processing ~s: ~p", [Command, Dir, Other]),
  204. ?FAIL
  205. end
  206. end.
  207. update_code_path(Config) ->
  208. case rebar_config:get(Config, lib_dirs, []) of
  209. [] ->
  210. no_change;
  211. Paths ->
  212. OldPath = code:get_path(),
  213. LibPaths = expand_lib_dirs(Paths, rebar_utils:get_cwd(), []),
  214. ok = code:add_pathsa(LibPaths),
  215. {old, OldPath}
  216. end.
  217. restore_code_path(no_change) ->
  218. ok;
  219. restore_code_path({old, Path}) ->
  220. %% Verify that all of the paths still exist -- some dynamically add paths
  221. %% can get blown away during clean.
  222. true = code:set_path(lists:filter(fun filelib:is_file/1, Path)),
  223. ok.
  224. expand_lib_dirs([], _Root, Acc) ->
  225. Acc;
  226. expand_lib_dirs([Dir | Rest], Root, Acc) ->
  227. Apps = filelib:wildcard(filename:join([Dir, '*', ebin])),
  228. FqApps = [filename:join([Root, A]) || A <- Apps],
  229. expand_lib_dirs(Rest, Root, Acc ++ FqApps).
  230. select_modules([], _Command, Acc) ->
  231. lists:reverse(Acc);
  232. select_modules([Module | Rest], Command, Acc) ->
  233. Exports = Module:module_info(exports),
  234. case lists:member({Command, 2}, Exports) of
  235. true ->
  236. select_modules(Rest, Command, [Module | Acc]);
  237. false ->
  238. select_modules(Rest, Command, Acc)
  239. end.
  240. run_modules([], _Command, _Config, _File) ->
  241. ok;
  242. run_modules([Module | Rest], Command, Config, File) ->
  243. case Module:Command(Config, File) of
  244. ok ->
  245. run_modules(Rest, Command, Config, File);
  246. {error, Reason} ->
  247. {error, Reason}
  248. end.
  249. acc_modules([], _Command, Config, _File, Acc) ->
  250. {Config, Acc};
  251. acc_modules([Module | Rest], Command, Config, File, Acc) ->
  252. case Module:Command(Config, File) of
  253. {ok, NewConfig, Result} when is_list(Result) ->
  254. List = Result;
  255. {ok, NewConfig, Result} ->
  256. List = [Result]
  257. end,
  258. acc_modules(Rest, Command, NewConfig, File, List ++ Acc).