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.

395 lines
14 KiB

преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 15 години
преди 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. %% Determine the location of the rebar executable; important for pulling
  56. %% resources out of the escript
  57. rebar_config:set_global(escript, filename:absname(escript:script_name())),
  58. ?DEBUG("Rebar location: ~p\n", [rebar_config:get_global(escript, undefined)]),
  59. %% Load rebar.config, if it exists
  60. process_dir(rebar_utils:get_cwd(), rebar_config:new(), CommandAtoms).
  61. %% ===================================================================
  62. %% Internal functions
  63. %% ===================================================================
  64. %%
  65. %% Parse command line arguments using getopt and also filtering out any
  66. %% key=value pairs. What's left is the list of commands to run
  67. %%
  68. parse_args(Args) ->
  69. %% Parse getopt options
  70. OptSpecList = option_spec_list(),
  71. case getopt:parse(OptSpecList, Args) of
  72. {ok, {Options, NonOptArgs}} ->
  73. %% Check options and maybe halt execution
  74. {ok, continue} = print_help_maybe_halt(Options, NonOptArgs),
  75. %% Set global variables based on getopt options
  76. set_global_flag(Options, verbose),
  77. set_global_flag(Options, force),
  78. DefJobs = rebar_config:get_jobs(),
  79. case proplists:get_value(jobs, Options, DefJobs) of
  80. DefJobs ->
  81. ok;
  82. Jobs ->
  83. rebar_config:set_global(jobs, Jobs)
  84. end,
  85. %% Filter all the flags (i.e. strings of form key=value) from the
  86. %% command line arguments. What's left will be the commands to run.
  87. filter_flags(NonOptArgs, []);
  88. {error, {Reason, Data}} ->
  89. ?ERROR("Error: ~s ~p~n~n", [Reason, Data]),
  90. help(),
  91. halt(1)
  92. end.
  93. %%
  94. %% set global flag based on getopt option boolean value
  95. %%
  96. set_global_flag(Options, Flag) ->
  97. Value = case proplists:get_bool(Flag, Options) of
  98. true ->
  99. "1";
  100. false ->
  101. "0"
  102. end,
  103. rebar_config:set_global(Flag, Value).
  104. %%
  105. %% print help and maybe halt execution
  106. %%
  107. print_help_maybe_halt(Options, NonOptArgs) ->
  108. case proplists:get_bool(help, Options) of
  109. true ->
  110. help(),
  111. halt(0);
  112. false ->
  113. case proplists:get_bool(commands, Options) of
  114. true ->
  115. commands(),
  116. halt(0);
  117. false ->
  118. case NonOptArgs of
  119. [] ->
  120. io:format("No command to run specified!~n"),
  121. help(),
  122. halt(1);
  123. _ ->
  124. {ok, continue}
  125. end
  126. end
  127. end.
  128. %%
  129. %% print help/usage string
  130. %%
  131. help() ->
  132. OptSpecList = option_spec_list(),
  133. getopt:usage(OptSpecList, "rebar",
  134. "[var=value,...] <command,...>",
  135. [{"var=value", "rebar global variables (e.g. force=1)"},
  136. {"command", "Command to run (e.g. compile)"}]).
  137. %%
  138. %% print known commands
  139. %%
  140. commands() ->
  141. S = <<"
  142. analyze Analyze with Dialyzer
  143. build_plt Build Dialyzer PLT
  144. check_plt Check Dialyzer PLT
  145. clean Clean
  146. compile Compile sources
  147. create template= [var=foo,...] Create skel based on template and vars
  148. create-app Create simple app skel
  149. create-node Create simple node skel
  150. check-deps Display to be fetched dependencies
  151. get-deps Fetch dependencies
  152. delete-deps Delete fetched dependencies
  153. generate [dump_spec=0/1] Build release with reltool
  154. install [target=] Install build into target
  155. eunit [suite=foo] Run eunit [test/foo_tests.erl] tests
  156. int_test [suite=] [case=] Run ct suites in ./int_test
  157. perf_test [suite=] [case=] Run ct suites in ./perf_test
  158. test [suite=] [case=] Run ct suites in ./test
  159. xref Run cross reference analysis
  160. ">>,
  161. io:put_chars(S),
  162. %% workaround to delay exit until all output is written
  163. timer:sleep(300).
  164. %%
  165. %% options accepted via getopt
  166. %%
  167. option_spec_list() ->
  168. Jobs = rebar_config:get_jobs(),
  169. JobsHelp = io_lib:format(
  170. "Number of concurrent workers a command may use. Default: ~B",
  171. [Jobs]),
  172. [
  173. %% {Name, ShortOpt, LongOpt, ArgSpec, HelpMsg}
  174. {help, $h, "help", undefined, "Show the program options"},
  175. {commands, $c, "commands", undefined, "Show available commands"},
  176. {verbose, $v, "verbose", undefined, "Be verbose about what gets done"},
  177. {force, $f, "force", undefined, "Force"},
  178. {jobs, $j, "jobs", integer, JobsHelp}
  179. ].
  180. %%
  181. %% Seperate all commands (single-words) from flags (key=value) and store
  182. %% values into the rebar_config global storage.
  183. %%
  184. filter_flags([], Commands) ->
  185. lists:reverse(Commands);
  186. filter_flags([Item | Rest], Commands) ->
  187. case string:tokens(Item, "=") of
  188. [Command] ->
  189. filter_flags(Rest, [Command | Commands]);
  190. [KeyStr, Value] ->
  191. Key = list_to_atom(KeyStr),
  192. rebar_config:set_global(Key, Value),
  193. filter_flags(Rest, Commands);
  194. Other ->
  195. ?CONSOLE("Ignoring command line argument: ~p\n", [Other]),
  196. filter_flags(Rest, Commands)
  197. end.
  198. process_dir(Dir, ParentConfig, Commands) ->
  199. case filelib:is_dir(Dir) of
  200. false ->
  201. ?WARN("Skipping non-existent sub-dir: ~p\n", [Dir]),
  202. ok;
  203. true ->
  204. ok = file:set_cwd(Dir),
  205. Config = rebar_config:new(ParentConfig),
  206. %% Save the current code path and then update it with
  207. %% lib_dirs. Children inherit parents code path, but we
  208. %% also want to ensure that we restore everything to pristine
  209. %% condition after processing this child
  210. CurrentCodePath = update_code_path(Config),
  211. %% Get the list of processing modules and check each one against
  212. %% CWD to see if it's a fit -- if it is, use that set of modules
  213. %% to process this dir.
  214. {ok, AvailModuleSets} = application:get_env(rebar, modules),
  215. {DirModules, ModuleSetFile} = choose_module_set(AvailModuleSets, Dir),
  216. %% Get the list of modules for "any dir". This is a catch-all list of modules
  217. %% that are processed in addion to modules associated with this directory
  218. %% type. These any_dir modules are processed FIRST.
  219. {ok, AnyDirModules} = application:get_env(rebar, any_dir_modules),
  220. Modules = AnyDirModules ++ DirModules,
  221. %% Give the modules a chance to tweak config and indicate if there
  222. %% are any other dirs that might need processing first.
  223. {UpdatedConfig, Dirs} = acc_modules(select_modules(Modules, preprocess, []),
  224. preprocess, Config, ModuleSetFile, []),
  225. ?DEBUG("~s subdirs: ~p\n", [Dir, Dirs]),
  226. [process_dir(D, UpdatedConfig, Commands) || D <- Dirs],
  227. %% Make sure the CWD is reset properly; processing subdirs may have caused it
  228. %% to change
  229. ok = file:set_cwd(Dir),
  230. %% Finally, process the current working directory
  231. ?DEBUG("Commands: ~p Modules: ~p\n", [Commands, Modules]),
  232. apply_commands(Commands, Modules, UpdatedConfig, ModuleSetFile),
  233. %% Once we're all done processing, reset the code path to whatever
  234. %% the parent initialized it to
  235. restore_code_path(CurrentCodePath),
  236. ok
  237. end.
  238. %%
  239. %% Given a list of module sets from rebar.app and a directory, find
  240. %% the appropriate subset of modules for this directory
  241. %%
  242. choose_module_set([], _Dir) ->
  243. {[], undefined};
  244. choose_module_set([{Fn, Modules} | Rest], Dir) ->
  245. case ?MODULE:Fn(Dir) of
  246. {true, File} ->
  247. {Modules, File};
  248. false ->
  249. choose_module_set(Rest, Dir)
  250. end.
  251. %%
  252. %% Return .app file if the current directory is an OTP app
  253. %%
  254. app_dir(Dir) ->
  255. rebar_app_utils:is_app_dir(Dir).
  256. %%
  257. %% Return the reltool.config file if the current directory is release directory
  258. %%
  259. rel_dir(Dir) ->
  260. rebar_rel_utils:is_rel_dir(Dir).
  261. apply_commands([], _Modules, _Config, _ModuleFile) ->
  262. ok;
  263. apply_commands([Command | Rest], Modules, Config, ModuleFile) ->
  264. case select_modules(Modules, Command, []) of
  265. [] ->
  266. ?CONSOLE("WARNING: ~p command does not apply to directory ~s\n",
  267. [Command, rebar_utils:get_cwd()]),
  268. apply_commands(Rest, Modules, Config, ModuleFile);
  269. TargetModules ->
  270. %% Provide some info on where we are
  271. Dir = rebar_utils:get_cwd(),
  272. ?CONSOLE("==> ~s (~s)\n", [filename:basename(Dir), Command]),
  273. %% Run the available modules
  274. case catch(run_modules(TargetModules, Command, Config, ModuleFile)) of
  275. ok ->
  276. apply_commands(Rest, Modules, Config, ModuleFile);
  277. {error, failed} ->
  278. ?FAIL;
  279. Other ->
  280. ?ABORT("~p failed while processing ~s: ~s",
  281. [Command, Dir, io_lib:print(Other, 1,80,-1)])
  282. end
  283. end.
  284. update_code_path(Config) ->
  285. case rebar_config:get(Config, lib_dirs, []) of
  286. [] ->
  287. no_change;
  288. Paths ->
  289. OldPath = code:get_path(),
  290. LibPaths = expand_lib_dirs(Paths, rebar_utils:get_cwd(), []),
  291. ok = code:add_pathsa(LibPaths),
  292. {old, OldPath}
  293. end.
  294. restore_code_path(no_change) ->
  295. ok;
  296. restore_code_path({old, Path}) ->
  297. %% Verify that all of the paths still exist -- some dynamically add paths
  298. %% can get blown away during clean.
  299. true = code:set_path(lists:filter(fun filelib:is_file/1, Path)),
  300. ok.
  301. expand_lib_dirs([], _Root, Acc) ->
  302. Acc;
  303. expand_lib_dirs([Dir | Rest], Root, Acc) ->
  304. Apps = filelib:wildcard(filename:join([Dir, '*', ebin])),
  305. FqApps = [filename:join([Root, A]) || A <- Apps],
  306. expand_lib_dirs(Rest, Root, Acc ++ FqApps).
  307. select_modules([], _Command, Acc) ->
  308. lists:reverse(Acc);
  309. select_modules([Module | Rest], Command, Acc) ->
  310. Exports = Module:module_info(exports),
  311. case lists:member({Command, 2}, Exports) of
  312. true ->
  313. select_modules(Rest, Command, [Module | Acc]);
  314. false ->
  315. select_modules(Rest, Command, Acc)
  316. end.
  317. run_modules([], _Command, _Config, _File) ->
  318. ok;
  319. run_modules([Module | Rest], Command, Config, File) ->
  320. case Module:Command(Config, File) of
  321. ok ->
  322. run_modules(Rest, Command, Config, File);
  323. {error, Reason} ->
  324. {error, Reason}
  325. end.
  326. acc_modules([], _Command, Config, _File, Acc) ->
  327. {Config, Acc};
  328. acc_modules([Module | Rest], Command, Config, File, Acc) ->
  329. case Module:Command(Config, File) of
  330. {ok, NewConfig, Result} when is_list(Result) ->
  331. List = Result;
  332. {ok, NewConfig, Result} ->
  333. List = [Result]
  334. end,
  335. acc_modules(Rest, Command, NewConfig, File, List ++ Acc).