Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

508 rader
18 KiB

14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
14 år sedan
  1. %% -*- 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([process_commands/2,
  29. skip_dir/1,
  30. is_skip_dir/1,
  31. skip_dirs/0]).
  32. -include("rebar.hrl").
  33. %% ===================================================================
  34. %% Public API
  35. %% ===================================================================
  36. skip_dir(Dir) ->
  37. SkipDir = {skip_dir, Dir},
  38. case erlang:get(SkipDir) of
  39. undefined ->
  40. ?DEBUG("Adding skip dir: ~s\n", [Dir]),
  41. erlang:put(SkipDir, true);
  42. true ->
  43. ok
  44. end.
  45. is_skip_dir(Dir) ->
  46. case erlang:get({skip_dir, Dir}) of
  47. undefined ->
  48. false;
  49. true ->
  50. true
  51. end.
  52. skip_dirs() ->
  53. [Dir || {{skip_dir, Dir}, true} <- erlang:get()].
  54. %% ===================================================================
  55. %% Internal functions
  56. %% ===================================================================
  57. process_commands([], _ParentConfig) ->
  58. case erlang:get(operations) of
  59. 0 ->
  60. %% none of the commands had an effect
  61. ?FAIL;
  62. _ ->
  63. ok
  64. end;
  65. process_commands([Command | Rest], ParentConfig) ->
  66. %% Reset skip dirs
  67. lists:foreach(fun (D) -> erlang:erase({skip_dir, D}) end, skip_dirs()),
  68. Operations = erlang:get(operations),
  69. %% Convert the code path so that all the entries are absolute paths.
  70. %% If not, code:set_path() may choke on invalid relative paths when trying
  71. %% to restore the code path from inside a subdirectory.
  72. true = rebar_utils:expand_code_path(),
  73. _ = process_dir(rebar_utils:get_cwd(), ParentConfig,
  74. Command, sets:new()),
  75. case erlang:get(operations) of
  76. Operations ->
  77. %% This command didn't do anything
  78. ?CONSOLE("Command '~p' not understood or not applicable~n",
  79. [Command]);
  80. _ ->
  81. ok
  82. end,
  83. process_commands(Rest, ParentConfig).
  84. process_dir(Dir, ParentConfig, Command, DirSet) ->
  85. case filelib:is_dir(Dir) of
  86. false ->
  87. ?WARN("Skipping non-existent sub-dir: ~p\n", [Dir]),
  88. DirSet;
  89. true ->
  90. ?DEBUG("Entering ~s\n", [Dir]),
  91. ok = file:set_cwd(Dir),
  92. Config = maybe_load_local_config(Dir, ParentConfig),
  93. %% Save the current code path and then update it with
  94. %% lib_dirs. Children inherit parents code path, but we
  95. %% also want to ensure that we restore everything to pristine
  96. %% condition after processing this child
  97. CurrentCodePath = update_code_path(Config),
  98. %% Get the list of processing modules and check each one against
  99. %% CWD to see if it's a fit -- if it is, use that set of modules
  100. %% to process this dir.
  101. {ok, AvailModuleSets} = application:get_env(rebar, modules),
  102. ModuleSet = choose_module_set(AvailModuleSets, Dir),
  103. maybe_process_dir(ModuleSet, Config, CurrentCodePath,
  104. Dir, Command, DirSet)
  105. end.
  106. maybe_process_dir({[], undefined}=ModuleSet, Config, CurrentCodePath,
  107. Dir, Command, DirSet) ->
  108. process_dir0(Dir, Command, DirSet, Config, CurrentCodePath, ModuleSet);
  109. maybe_process_dir({_, ModuleSetFile}=ModuleSet, Config, CurrentCodePath,
  110. Dir, Command, DirSet) ->
  111. case lists:suffix(".app.src", ModuleSetFile)
  112. orelse lists:suffix(".app", ModuleSetFile) of
  113. true ->
  114. %% .app or .app.src file, check if is_skipped_app
  115. maybe_process_dir0(ModuleSetFile, ModuleSet,
  116. Config, CurrentCodePath, Dir,
  117. Command, DirSet);
  118. false ->
  119. %% not an app dir, no need to consider apps=/skip_apps=
  120. process_dir0(Dir, Command, DirSet, Config,
  121. CurrentCodePath, ModuleSet)
  122. end.
  123. maybe_process_dir0(AppFile, ModuleSet, Config, CurrentCodePath,
  124. Dir, Command, DirSet) ->
  125. case rebar_app_utils:is_skipped_app(AppFile) of
  126. {true, SkippedApp} ->
  127. ?DEBUG("Skipping app: ~p~n", [SkippedApp]),
  128. increment_operations(),
  129. DirSet;
  130. false ->
  131. process_dir0(Dir, Command, DirSet, Config,
  132. CurrentCodePath, ModuleSet)
  133. end.
  134. process_dir0(Dir, Command, DirSet, Config, CurrentCodePath,
  135. {DirModules, ModuleSetFile}) ->
  136. %% Get the list of modules for "any dir". This is a catch-all list
  137. %% of modules that are processed in addition to modules associated
  138. %% with this directory type. These any_dir modules are processed
  139. %% FIRST.
  140. {ok, AnyDirModules} = application:get_env(rebar, any_dir_modules),
  141. Modules = AnyDirModules ++ DirModules,
  142. %% Invoke 'preprocess' on the modules -- this yields a list of other
  143. %% directories that should be processed _before_ the current one.
  144. Predirs = acc_modules(Modules, preprocess, Config, ModuleSetFile),
  145. %% Get the list of plug-in modules from rebar.config. These
  146. %% modules may participate in preprocess and postprocess.
  147. {ok, PluginModules} = plugin_modules(Config),
  148. PluginPredirs = acc_modules(PluginModules, preprocess,
  149. Config, ModuleSetFile),
  150. AllPredirs = Predirs ++ PluginPredirs,
  151. ?DEBUG("Predirs: ~p\n", [AllPredirs]),
  152. DirSet2 = process_each(AllPredirs, Command, Config,
  153. ModuleSetFile, DirSet),
  154. %% Make sure the CWD is reset properly; processing the dirs may have
  155. %% caused it to change
  156. ok = file:set_cwd(Dir),
  157. %% Check that this directory is not on the skip list
  158. case is_skip_dir(Dir) of
  159. true ->
  160. %% Do not execute the command on the directory, as some
  161. %% module as requested a skip on it.
  162. ?INFO("Skipping ~s in ~s\n", [Command, Dir]);
  163. false ->
  164. %% Execute any before_command plugins on this directory
  165. execute_pre(Command, PluginModules,
  166. Config, ModuleSetFile),
  167. %% Execute the current command on this directory
  168. execute(Command, Modules ++ PluginModules,
  169. Config, ModuleSetFile),
  170. %% Execute any after_command plugins on this directory
  171. execute_post(Command, PluginModules,
  172. Config, ModuleSetFile)
  173. end,
  174. %% Mark the current directory as processed
  175. DirSet3 = sets:add_element(Dir, DirSet2),
  176. %% Invoke 'postprocess' on the modules. This yields a list of other
  177. %% directories that should be processed _after_ the current one.
  178. Postdirs = acc_modules(Modules ++ PluginModules, postprocess,
  179. Config, ModuleSetFile),
  180. ?DEBUG("Postdirs: ~p\n", [Postdirs]),
  181. DirSet4 = process_each(Postdirs, Command, Config,
  182. ModuleSetFile, DirSet3),
  183. %% Make sure the CWD is reset properly; processing the dirs may have
  184. %% caused it to change
  185. ok = file:set_cwd(Dir),
  186. %% Once we're all done processing, reset the code path to whatever
  187. %% the parent initialized it to
  188. restore_code_path(CurrentCodePath),
  189. %% Return the updated dirset as our result
  190. DirSet4.
  191. maybe_load_local_config(Dir, ParentConfig) ->
  192. %% We need to ensure we don't overwrite custom
  193. %% config when we are dealing with base_dir.
  194. case processing_base_dir(Dir) of
  195. true ->
  196. ParentConfig;
  197. false ->
  198. rebar_config:new(ParentConfig)
  199. end.
  200. processing_base_dir(Dir) ->
  201. Dir == rebar_config:get_global(base_dir, undefined).
  202. %%
  203. %% Given a list of directories and a set of previously processed directories,
  204. %% process each one we haven't seen yet
  205. %%
  206. process_each([], _Command, _Config, _ModuleSetFile, DirSet) ->
  207. DirSet;
  208. process_each([Dir | Rest], Command, Config, ModuleSetFile, DirSet) ->
  209. case sets:is_element(Dir, DirSet) of
  210. true ->
  211. ?DEBUG("Skipping ~s; already processed!\n", [Dir]),
  212. process_each(Rest, Command, Config, ModuleSetFile, DirSet);
  213. false ->
  214. DirSet2 = process_dir(Dir, Config, Command, DirSet),
  215. process_each(Rest, Command, Config, ModuleSetFile, DirSet2)
  216. end.
  217. %%
  218. %% Given a list of module sets from rebar.app and a directory, find
  219. %% the appropriate subset of modules for this directory
  220. %%
  221. choose_module_set([], _Dir) ->
  222. {[], undefined};
  223. choose_module_set([{Type, Modules} | Rest], Dir) ->
  224. case is_dir_type(Type, Dir) of
  225. {true, File} ->
  226. {Modules, File};
  227. false ->
  228. choose_module_set(Rest, Dir)
  229. end.
  230. is_dir_type(app_dir, Dir) ->
  231. rebar_app_utils:is_app_dir(Dir);
  232. is_dir_type(rel_dir, Dir) ->
  233. rebar_rel_utils:is_rel_dir(Dir);
  234. is_dir_type(_, _) ->
  235. false.
  236. execute_pre(Command, Modules, Config, ModuleFile) ->
  237. execute_plugin_hook("pre_", Command, Modules,
  238. Config, ModuleFile).
  239. execute_post(Command, Modules, Config, ModuleFile) ->
  240. execute_plugin_hook("post_", Command, Modules,
  241. Config, ModuleFile).
  242. execute_plugin_hook(Hook, Command, Modules, Config, ModuleFile) ->
  243. HookFunction = list_to_atom(Hook ++ atom_to_list(Command)),
  244. execute(HookFunction, Modules, Config, ModuleFile).
  245. %%
  246. %% Execute a command across all applicable modules
  247. %%
  248. execute(Command, Modules, Config, ModuleFile) ->
  249. case select_modules(Modules, Command, []) of
  250. [] ->
  251. Cmd = atom_to_list(Command),
  252. case lists:prefix("pre_", Cmd)
  253. orelse lists:prefix("post_", Cmd) of
  254. true ->
  255. ok;
  256. false ->
  257. ?WARN("'~p' command does not apply to directory ~s\n",
  258. [Command, rebar_utils:get_cwd()])
  259. end;
  260. TargetModules ->
  261. %% Provide some info on where we are
  262. Dir = rebar_utils:get_cwd(),
  263. ?CONSOLE("==> ~s (~s)\n", [filename:basename(Dir), Command]),
  264. increment_operations(),
  265. %% Check for and get command specific environments
  266. Env = setup_envs(Config, Modules),
  267. %% Run the available modules
  268. apply_hooks(pre_hooks, Config, Command, Env),
  269. case catch(run_modules(TargetModules, Command,
  270. Config, ModuleFile)) of
  271. ok ->
  272. apply_hooks(post_hooks, Config, Command, Env),
  273. ok;
  274. {error, failed} ->
  275. ?FAIL;
  276. {Module, {error, _} = Other} ->
  277. ?ABORT("~p failed while processing ~s in module ~s: ~s\n",
  278. [Command, Dir, Module,
  279. io_lib:print(Other, 1, 80, -1)]);
  280. Other ->
  281. ?ABORT("~p failed while processing ~s: ~s\n",
  282. [Command, Dir, io_lib:print(Other, 1, 80, -1)])
  283. end
  284. end.
  285. %% Increment the count of operations, since some module
  286. %% responds to this command
  287. increment_operations() ->
  288. erlang:put(operations, erlang:get(operations) + 1).
  289. update_code_path(Config) ->
  290. case rebar_config:get_local(Config, lib_dirs, []) of
  291. [] ->
  292. no_change;
  293. Paths ->
  294. OldPath = code:get_path(),
  295. LibPaths = expand_lib_dirs(Paths, rebar_utils:get_cwd(), []),
  296. ok = code:add_pathsa(LibPaths),
  297. {old, OldPath}
  298. end.
  299. restore_code_path(no_change) ->
  300. ok;
  301. restore_code_path({old, Path}) ->
  302. %% Verify that all of the paths still exist -- some dynamically
  303. %% added paths can get blown away during clean.
  304. true = code:set_path([F || F <- Path, filelib:is_file(F)]),
  305. ok.
  306. expand_lib_dirs([], _Root, Acc) ->
  307. Acc;
  308. expand_lib_dirs([Dir | Rest], Root, Acc) ->
  309. Apps = filelib:wildcard(filename:join([Dir, "*", "ebin"])),
  310. FqApps = [filename:join([Root, A]) || A <- Apps],
  311. expand_lib_dirs(Rest, Root, Acc ++ FqApps).
  312. select_modules([], _Command, Acc) ->
  313. lists:reverse(Acc);
  314. select_modules([Module | Rest], Command, Acc) ->
  315. {module, Module} = code:ensure_loaded(Module),
  316. case erlang:function_exported(Module, Command, 2) of
  317. true ->
  318. select_modules(Rest, Command, [Module | Acc]);
  319. false ->
  320. select_modules(Rest, Command, Acc)
  321. end.
  322. run_modules([], _Command, _Config, _File) ->
  323. ok;
  324. run_modules([Module | Rest], Command, Config, File) ->
  325. case Module:Command(Config, File) of
  326. ok ->
  327. run_modules(Rest, Command, Config, File);
  328. {error, _} = Error ->
  329. {Module, Error}
  330. end.
  331. apply_hooks(Mode, Config, Command, Env) ->
  332. Hooks = rebar_config:get_local(Config, Mode, []),
  333. lists:foreach(fun apply_hook/1,
  334. [{Env, Hook} || Hook <- Hooks,
  335. element(1, Hook) =:= Command orelse
  336. element(2, Hook) =:= Command]).
  337. apply_hook({Env, {Arch, Command, Hook}}) ->
  338. case rebar_utils:is_arch(Arch) of
  339. true ->
  340. apply_hook({Env, {Command, Hook}});
  341. false ->
  342. ok
  343. end;
  344. apply_hook({Env, {Command, Hook}}) ->
  345. Msg = lists:flatten(io_lib:format("Command [~p] failed!~n", [Command])),
  346. rebar_utils:sh(Hook, [{env, Env}, {abort_on_error, Msg}]).
  347. setup_envs(Config, Modules) ->
  348. lists:flatten([M:setup_env(Config) ||
  349. M <- Modules,
  350. erlang:function_exported(M, setup_env, 1)]).
  351. acc_modules(Modules, Command, Config, File) ->
  352. acc_modules(select_modules(Modules, Command, []),
  353. Command, Config, File, []).
  354. acc_modules([], _Command, _Config, _File, Acc) ->
  355. Acc;
  356. acc_modules([Module | Rest], Command, Config, File, Acc) ->
  357. {ok, Dirs} = Module:Command(Config, File),
  358. acc_modules(Rest, Command, Config, File, Acc ++ Dirs).
  359. %%
  360. %% Return a flat list of rebar plugin modules.
  361. %%
  362. plugin_modules(Config) ->
  363. Modules = lists:flatten(rebar_config:get_all(Config, plugins)),
  364. plugin_modules(Config, ulist(Modules)).
  365. ulist(L) ->
  366. ulist(L, []).
  367. ulist([], Acc) ->
  368. lists:reverse(Acc);
  369. ulist([H | T], Acc) ->
  370. case lists:member(H, Acc) of
  371. true ->
  372. ulist(T, Acc);
  373. false ->
  374. ulist(T, [H | Acc])
  375. end.
  376. plugin_modules(_Config, []) ->
  377. {ok, []};
  378. plugin_modules(Config, Modules) ->
  379. FoundModules = [M || M <- Modules, code:which(M) =/= non_existing],
  380. plugin_modules(Config, FoundModules, Modules -- FoundModules).
  381. plugin_modules(_Config, FoundModules, []) ->
  382. {ok, FoundModules};
  383. plugin_modules(Config, FoundModules, MissingModules) ->
  384. {Loaded, NotLoaded} = load_plugin_modules(Config, MissingModules),
  385. AllViablePlugins = FoundModules ++ Loaded,
  386. case NotLoaded =/= [] of
  387. true ->
  388. %% NB: we continue to ignore this situation, as did the original code
  389. ?WARN("Missing plugins: ~p\n", [NotLoaded]);
  390. false ->
  391. ?DEBUG("Loaded plugins: ~p~n", [AllViablePlugins]),
  392. ok
  393. end,
  394. {ok, AllViablePlugins}.
  395. load_plugin_modules(Config, Modules) ->
  396. PluginDir = case rebar_config:get_local(Config, plugin_dir, undefined) of
  397. undefined ->
  398. filename:join(rebar_utils:get_cwd(), "plugins");
  399. Dir ->
  400. Dir
  401. end,
  402. %% Find relevant sources in base_dir and plugin_dir
  403. Erls = string:join([atom_to_list(M)++"\\.erl" || M <- Modules], "|"),
  404. RE = "^" ++ Erls ++ "\$",
  405. BaseDir = rebar_config:get_global(base_dir, []),
  406. %% If a plugin is found in base_dir and plugin_dir the clash
  407. %% will provoke an error and we'll abort.
  408. Sources = rebar_utils:find_files(PluginDir, RE, false)
  409. ++ rebar_utils:find_files(BaseDir, RE, false),
  410. %% Compile and load plugins
  411. Loaded = [load_plugin(Src) || Src <- Sources],
  412. FilterMissing = is_missing_plugin(Loaded),
  413. NotLoaded = [V || V <- Modules, FilterMissing(V)],
  414. {Loaded, NotLoaded}.
  415. is_missing_plugin(Loaded) ->
  416. fun(Mod) -> not lists:member(Mod, Loaded) end.
  417. load_plugin(Src) ->
  418. case compile:file(Src, [binary, return_errors]) of
  419. {ok, Mod, Bin} ->
  420. load_plugin_module(Mod, Bin, Src);
  421. {error, Errors, _Warnings} ->
  422. ?ABORT("Plugin ~s contains compilation errors: ~p~n",
  423. [Src, Errors])
  424. end.
  425. load_plugin_module(Mod, Bin, Src) ->
  426. case code:is_loaded(Mod) of
  427. {file, Loaded} ->
  428. ?ABORT("Plugin ~p clashes with previously loaded module ~p~n",
  429. [Mod, Loaded]);
  430. false ->
  431. ?INFO("Loading plugin ~p from ~s~n", [Mod, Src]),
  432. {module, Mod} = code:load_binary(Mod, Src, Bin),
  433. Mod
  434. end.