PageRenderTime 51ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/boss/boss_load.erl

http://github.com/evanmiller/ChicagoBoss
Erlang | 509 lines | 431 code | 64 blank | 14 comment | 3 complexity | d858a4319abb526266cb10022dac8e43 MD5 | raw file
  1. %%-------------------------------------------------------------------
  2. %% @author
  3. %% ChicagoBoss Team and contributors, see AUTHORS file in root directory
  4. %% @end
  5. %% @copyright
  6. %% This file is part of ChicagoBoss project.
  7. %% See AUTHORS file in root directory
  8. %% for license information, see LICENSE file in root directory
  9. %% @end
  10. %% @doc
  11. %%-------------------------------------------------------------------
  12. -module(boss_load).
  13. -export([
  14. incoming_mail_controller_module/1,
  15. load_all_modules/2,
  16. load_all_modules/3,
  17. load_all_modules_and_emit_app_file/2,
  18. load_libraries/1,
  19. load_services_websockets/1,
  20. load_mail_controllers/1,
  21. load_models/1,
  22. load_view_if_dev/4,
  23. load_view_lib_modules/1,
  24. load_web_controllers/1,
  25. module_is_loaded/1,
  26. reload_all/0
  27. ]).
  28. -include("boss_web.hrl").
  29. -ifdef(TEST).
  30. -compile(export_all).
  31. -endif.
  32. -type module_types() :: [{'controller_modules' | 'lib_modules' |
  33. 'mail_modules' | 'model_modules' | 'test_modules' |
  34. 'view_lib_helper_modules' | 'view_lib_tags_modules' | 'view_modules'
  35. | 'websocket_modules',maybe_improper_list()},...].
  36. -type reload_error_status_values() :: 'badfile' | 'native_code' | 'nofile' | 'not_purged' | 'on_load' | 'sticky_directory'.
  37. -type application() :: types:application().
  38. -spec incoming_mail_controller_module(application()) -> atom().
  39. -spec load_all_modules(application(), atom() | pid() | {atom(),atom()}) -> {'ok',module_types()}.
  40. -spec load_all_modules(application(),atom() | pid() | {atom(),atom()},_) -> {'ok',module_types()}.
  41. -spec load_all_modules_and_emit_app_file(atom(),string()) -> 'ok' | {'error',atom()}.
  42. -spec load_libraries(application()) -> {'error',[any(),...]} | {'ok',[any()]}.
  43. -spec load_mail_controllers(application()) -> {'error',[any(),...]} | {'ok',[any()]}.
  44. -spec load_models(application()) -> {'error',[any(),...]} | {'ok',[any()]}.
  45. -spec load_services_websockets(application()) -> {'error',[any(),...]} | {'ok',[any()]}.
  46. -spec load_view_if_dev(application(), atom() | binary() | [atom() | [any()] | char()],_,_) -> any().
  47. -spec load_view_lib_modules(application()) -> {'error',[any(),...]} | {'ok',[any()]}.
  48. -spec load_web_controllers(application()) -> {'error',[any(),...]} | {'ok',[any()]}.
  49. -spec module_is_loaded(atom()) -> boolean().
  50. -spec reload_all() -> [{'error',reload_error_status_values()}|
  51. {'module', atom() | tuple()}].
  52. -define(CUSTOM_TAGS_DIR_MODULE, '_view_lib_tags').
  53. load_all_modules(Application, TranslatorSupPid) ->
  54. load_all_modules(Application, TranslatorSupPid, boss_files_util:ebin_dir()).
  55. load_all_modules(Application, TranslatorSupPid, OutDir) ->
  56. _ = lager:debug("Loading application ~p", [Application]),
  57. [{_, TranslatorPid, _, _}] = supervisor:which_children(TranslatorSupPid),
  58. Ops = make_ops_list(TranslatorPid),
  59. AllModules = make_all_modules(Application, OutDir, Ops),
  60. {ok, AllModules}.
  61. -type error(X) :: {ok, X} | {error, string()}.
  62. -type op_key() :: test_modules|lib_modules|websocket_modules|mail_modules|controller_modules|
  63. model_modules| view_lib_tags_modules|view_lib_helper_modules|view_modules.
  64. -type op() :: {op_key(), fun((atom(), string()) -> error(_))}.
  65. -spec(make_ops_list(pid()) -> [op()]).
  66. make_ops_list(TranslatorPid) ->
  67. [{test_modules, fun load_test_modules/2 },
  68. {lib_modules, fun load_libraries/2 },
  69. {websocket_modules, fun load_services_websockets/2 },
  70. {mail_modules, fun load_mail_controllers/2 },
  71. {controller_modules, fun load_web_controllers/2 },
  72. {model_modules, fun load_models/2 },
  73. {view_lib_helper_modules, fun load_view_lib_modules/2 },
  74. {view_lib_tags_modules, load_view_lib(_, _, TranslatorPid)},
  75. {view_modules, load_views(_, _, TranslatorPid) }
  76. ].
  77. -spec make_all_modules(atom(), string(), [op()]) -> [{atom(),_}].
  78. make_all_modules(Application, OutDir, Ops) ->
  79. lists:map(fun({Key, Lambda}) ->
  80. case Lambda(Application, OutDir) of
  81. {ok, Modules} ->
  82. {Key, Modules};
  83. {error, Message} ->
  84. _ = lager:error("Load Module Error ~p : ~p", [Key, Message]),
  85. {Key, []}
  86. end
  87. end, Ops).
  88. load_test_modules(Application, OutDir) ->
  89. Result = load_dirs(boss_files_util:test_path(),
  90. Application,
  91. OutDir,
  92. fun compile/2),
  93. Result.
  94. load_all_modules_and_emit_app_file(AppName, OutDir) ->
  95. application:start(elixir),
  96. {ok, TranslatorSupPid} = boss_translator:start([{application, AppName}]),
  97. {ok, ModulePropList} = load_all_modules(AppName, TranslatorSupPid, OutDir),
  98. AllModules = lists:foldr(fun({_, Mods}, Acc) -> Mods ++ Acc end, [], ModulePropList),
  99. DotAppSrc = boss_files:dot_app_src(AppName),
  100. {ok, [{application, AppName, AppData}]} = file:consult(DotAppSrc),
  101. AppData1 = lists:keyreplace(modules, 1, AppData, {modules, AllModules}),
  102. DefaultEnv = proplists:get_value(env, AppData1, []),
  103. AppData2 = lists:keyreplace(env, 1, AppData1, {env, ModulePropList ++ DefaultEnv}),
  104. IOList = io_lib:format("~p.~n", [{application, AppName, AppData2}]),
  105. AppFile = filename:join([OutDir, lists:concat([AppName, ".app"])]),
  106. file:write_file(AppFile, IOList).
  107. make_computed_vsn({unknown, Val} ) ->Val;
  108. make_computed_vsn(Cmd ) ->
  109. VsnString = os:cmd(Cmd),
  110. string:strip(VsnString, right, $\n).
  111. reload_all() ->
  112. _ = lager:notice("Reload All"),
  113. Modules = [M || {M, F} <- code:all_loaded(), is_list(F), not code:is_sticky(M)],
  114. [begin
  115. code:purge(M),
  116. code:load_file(M)
  117. end || M <- Modules].
  118. load_libraries(Application) ->
  119. load_libraries(Application, boss_files_util:ebin_dir()).
  120. load_libraries(Application, OutDir) ->
  121. load_dirs(boss_files_util:lib_path(), Application, OutDir, fun compile/2).
  122. load_services_websockets(Application) ->
  123. load_services_websockets(Application, boss_files_util:ebin_dir()).
  124. load_services_websockets(Application, OutDir) ->
  125. load_dirs(boss_files_util:websocket_path(), Application, OutDir, fun compile/2).
  126. load_mail_controllers(Application) ->
  127. load_mail_controllers(Application, boss_files_util:ebin_dir()).
  128. load_mail_controllers(Application, OutDir) ->
  129. load_dirs(boss_files:mail_controller_path(), Application, OutDir, fun compile/2).
  130. load_web_controllers(Application) ->
  131. load_web_controllers(Application, boss_files_util:ebin_dir()).
  132. load_web_controllers(Application, OutDir) ->
  133. load_dirs(boss_files_util:web_controller_path(), Application, OutDir, fun compile_controller/2).
  134. load_view_lib_modules(Application) ->
  135. load_view_lib_modules(Application, boss_files_util:ebin_dir()).
  136. load_view_lib_modules(Application, OutDir) ->
  137. load_dirs(boss_files_util:view_helpers_path(), Application, OutDir, fun compile/2).
  138. load_models(Application) ->
  139. load_models(Application, boss_files_util:ebin_dir()).
  140. load_models(Application, OutDir) ->
  141. load_model_dirs(boss_files_util:model_path(), Application, OutDir, fun compile_model/2).
  142. load_model_dirs(Dirs, Application, OutDir, Compiler) ->
  143. load_model_dirs1(Dirs, Application, OutDir, Compiler, [], []).
  144. load_model_dirs1([], _, _, _, ModuleAcc, []) ->
  145. {ok, ModuleAcc};
  146. load_model_dirs1([], _, _, _, _, ErrorAcc) ->
  147. {error, ErrorAcc};
  148. load_model_dirs1([Dir|Rest], Application, OutDir, Compiler, ModuleAcc, ErrorAcc) ->
  149. case load_model_dir(Dir, Application, OutDir, Compiler) of
  150. {ok, ModuleList} ->
  151. load_model_dirs1(Rest, Application, OutDir, Compiler, ModuleList ++ ModuleAcc, ErrorAcc);
  152. {error, ErrorList} ->
  153. load_model_dirs1(Rest, Application, OutDir, Compiler, ModuleAcc, ErrorList ++ ErrorAcc)
  154. end.
  155. load_model_dir(Dir, Application, OutDir, Compiler) when is_function(Compiler) ->
  156. FullFiles = list_subfolder_files(Dir),
  157. {ModuleList, ErrorList} = compile_and_accumulate_errors(
  158. FullFiles, Application, OutDir, Compiler, {[], []}),
  159. case length(ErrorList) of
  160. 0 ->
  161. {ok, ModuleList};
  162. _ ->
  163. {error, ErrorList}
  164. end.
  165. %% Only serve files that end in ".erl", with sub folder
  166. %% todo: maybe remove .#*.erl file (emacs specific)
  167. list_subfolder_files(Dir) ->
  168. lists:filter(fun(String) ->
  169. string:right(String, 4) == ".erl"
  170. end, boss_files:find_file(Dir)).
  171. load_dirs(Dirs, Application, OutDir, Compiler) ->
  172. load_dirs1(Dirs, Application, OutDir, Compiler, [], []).
  173. load_dirs1([], _, _, _, ModuleAcc, []) ->
  174. {ok, ModuleAcc};
  175. load_dirs1([], _, _, _, _, ErrorAcc) ->
  176. {error, ErrorAcc};
  177. load_dirs1([Dir|Rest], Application, OutDir, Compiler, ModuleAcc, ErrorAcc) ->
  178. case load_dir(Dir, Application, OutDir, Compiler) of
  179. {ok, ModuleList} ->
  180. load_dirs1(Rest, Application, OutDir, Compiler, ModuleList ++ ModuleAcc, ErrorAcc);
  181. {error, ErrorList} ->
  182. load_dirs1(Rest, Application, OutDir, Compiler, ModuleAcc, ErrorList ++ ErrorAcc)
  183. end.
  184. load_dir(Dir, Application, OutDir, Compiler) when is_function(Compiler) ->
  185. Files = list_files(Dir),
  186. FullFiles = lists:map(fun(F) -> filename:join([Dir, F]) end, Files),
  187. {ModuleList, ErrorList} = compile_and_accumulate_errors(
  188. FullFiles, Application, OutDir, Compiler, {[], []}),
  189. case length(ErrorList) of
  190. 0 ->
  191. {ok, ModuleList};
  192. _ ->
  193. {error, ErrorList}
  194. end.
  195. %% Only serve files that end in ".erl" or ".ex"
  196. list_files(Dir) ->
  197. case file:list_dir(Dir) of
  198. {ok, FileList} ->
  199. lists:filter(fun(String) ->
  200. Ext = filename:extension(String),
  201. lists:member(Ext, [".erl", ".ex"])
  202. end, FileList);
  203. _ ->
  204. []
  205. end.
  206. compile_and_accumulate_errors([], _Application, _OutDir, _Compiler, Acc) ->
  207. Acc;
  208. compile_and_accumulate_errors([Filename|Rest], Application, OutDir, Compiler, {Modules, Errors}) ->
  209. Result = case filename:basename(Filename) of
  210. "."++_ ->
  211. {Modules, Errors};
  212. _ ->
  213. case filelib:is_dir(Filename) of
  214. true ->
  215. case load_dir(Filename, Application, OutDir, Compiler) of
  216. {ok, NewMods} ->
  217. {NewMods ++ Modules, Errors};
  218. {error, NewErrs} ->
  219. {Modules, NewErrs ++ Errors}
  220. end;
  221. false ->
  222. CompileResult = maybe_compile(Filename, Application, OutDir, Compiler),
  223. case CompileResult of
  224. ok ->
  225. {Modules, Errors};
  226. {ok, Module} ->
  227. {[Module|Modules], Errors};
  228. {error, Error} ->
  229. _ = lager:error("Compile Error, ~p -> ~p", [Filename, Error]),
  230. {Modules, [Error | Errors]};
  231. {error, NewErrors, _NewWarnings} when is_list(NewErrors) ->
  232. _ = lager:error("Compile Error, ~p -> ~p", [Filename, NewErrors]),
  233. {Modules, NewErrors ++ Errors}
  234. end
  235. end
  236. end,
  237. compile_and_accumulate_errors(Rest, Application, OutDir, Compiler, Result).
  238. maybe_compile(File, Application, OutDir, Compiler) ->
  239. CompilerAdapter = boss_files:compiler_adapter_for_extension(filename:extension(File)),
  240. maybe_compile(File, Application, OutDir, Compiler, CompilerAdapter).
  241. maybe_compile(_File, _Application, _OutDir, _Compiler, undefined) -> ok;
  242. maybe_compile(File, Application, OutDir, Compiler, CompilerAdapter) ->
  243. Module = list_to_atom(CompilerAdapter:module_name_for_file(Application, File)),
  244. AbsPath = filename:absname(File),
  245. case boss_env:is_developing_app(Application) of
  246. true ->
  247. case module_older_than(Module, [AbsPath]) of
  248. true ->
  249. Compiler(AbsPath, OutDir);
  250. _ ->
  251. {ok, Module}
  252. end;
  253. _ ->
  254. Compiler(AbsPath, OutDir)
  255. end.
  256. view_doc_root(ViewPath) ->
  257. lists:foldl(fun
  258. (LibPath, Best) when length(LibPath) > length(Best) ->
  259. case lists:prefix(LibPath, ViewPath) of
  260. true ->
  261. LibPath;
  262. false ->
  263. Best
  264. end;
  265. (_, Best) ->
  266. Best
  267. end, "",
  268. [boss_files_util:web_view_path(), boss_files_util:mail_view_path()]).
  269. compile_view_dir_erlydtl(Application, LibPath, Module, OutDir, TranslatorPid) ->
  270. TagHelpers = lists:map(fun erlang:list_to_atom/1, boss_files_util:view_tag_helper_list(Application)),
  271. FilterHelpers = lists:map(fun erlang:list_to_atom/1, boss_files_util:view_filter_helper_list(Application)),
  272. ExtraTagHelpers = boss_env:get_env(template_tag_modules, []),
  273. ExtraFilterHelpers = boss_env:get_env(template_filter_modules, []),
  274. _ = lager:debug("Compile Modules ~p ~p", [LibPath, Module]),
  275. Res = erlydtl:compile_dir(LibPath, Module,
  276. [{doc_root, view_doc_root(LibPath)}, {compiler_options, []}, {out_dir, OutDir},
  277. {custom_tags_modules, TagHelpers ++ ExtraTagHelpers ++ [boss_erlydtl_tags]},
  278. {custom_filters_modules, FilterHelpers ++ ExtraFilterHelpers},
  279. {blocktrans_fun,
  280. fun(BlockString, Locale) ->
  281. case boss_translator:lookup(TranslatorPid, BlockString, Locale) of
  282. undefined -> default;
  283. Body -> list_to_binary(Body)
  284. end
  285. end}]),
  286. case Res of
  287. ok ->
  288. {ok, Module};
  289. Err -> Err
  290. end.
  291. compile_view(Application, ViewPath, TemplateAdapter, OutDir, TranslatorPid) ->
  292. case file:read_file_info(ViewPath) of
  293. {ok, _} ->
  294. Module = view_module(Application, ViewPath),
  295. HelperDirModule = view_custom_tags_dir_module(Application),
  296. Locales = boss_files:language_list(Application),
  297. DocRoot = view_doc_root(ViewPath),
  298. TagHelpers = lists:map(fun erlang:list_to_atom/1,
  299. boss_files_util:view_tag_helper_list(Application)),
  300. FilterHelpers = lists:map(fun erlang:list_to_atom/1,
  301. boss_files_util:view_filter_helper_list(Application)),
  302. TemplateAdapter:compile_file(ViewPath, Module, [
  303. {out_dir, OutDir},
  304. {doc_root, DocRoot},
  305. {translator_pid, TranslatorPid},
  306. {helper_module, HelperDirModule},
  307. {tag_helpers, TagHelpers},
  308. {filter_helpers, FilterHelpers},
  309. {locales, Locales}]);
  310. _ ->
  311. {error, not_found}
  312. end.
  313. compile_model(ModulePath, OutDir) ->
  314. IncludeDirs = [boss_files_util:include_dir() | boss_env:get_env(boss, include_dirs, [])],
  315. boss_model_manager:compile(ModulePath, [{out_dir, OutDir}, {include_dirs, IncludeDirs},
  316. {compiler_options, compiler_options()}]).
  317. compile_controller(ModulePath, OutDir) ->
  318. IncludeDirs = [boss_files_util:include_dir() | boss_env:get_env(boss, include_dirs, [])],
  319. Options = [{out_dir, OutDir}, {include_dirs, IncludeDirs}, {compiler_options, compiler_options()}],
  320. CompilerAdapter = boss_files:compiler_adapter_for_extension(filename:extension(ModulePath)),
  321. CompilerAdapter:compile_controller(ModulePath, Options).
  322. compile(ModulePath, OutDir) ->
  323. IncludeDirs = [boss_files_util:include_dir() | boss_env:get_env(boss, include_dirs, [])],
  324. Options = [{out_dir, OutDir}, {include_dirs, IncludeDirs}, {compiler_options, compiler_options()}],
  325. CompilerAdapter = boss_files:compiler_adapter_for_extension(filename:extension(ModulePath)),
  326. CompilerAdapter:compile(ModulePath, Options).
  327. compiler_options() ->
  328. lists:merge([{parse_transform, lager_transform}, return_errors],
  329. boss_env:get_env(boss, compiler_options, [])).
  330. load_view_lib(Application, OutDir, TranslatorPid) ->
  331. {ok, HelperDirModule} = compile_view_dir_erlydtl(Application,
  332. boss_files_util:view_html_tags_path(), view_custom_tags_dir_module(Application),
  333. OutDir, TranslatorPid),
  334. {ok, [HelperDirModule]}.
  335. load_view_lib_if_old(Application, OutDir, TranslatorPid) ->
  336. HelperDirModule = view_custom_tags_dir_module(Application),
  337. DirNeedsCompile = module_older_than(HelperDirModule, lists:map(fun
  338. ({File, _CheckSum}) -> File;
  339. (File) -> File
  340. end, [HelperDirModule:source_dir() | HelperDirModule:dependencies()])),
  341. case DirNeedsCompile of
  342. true ->
  343. load_view_lib(Application, OutDir, TranslatorPid);
  344. false ->
  345. {ok, [HelperDirModule]}
  346. end.
  347. load_views(Application, OutDir, TranslatorPid) ->
  348. ModuleList = lists:foldr(load_views_inner(Application, OutDir,
  349. TranslatorPid),
  350. [], boss_files:view_file_list()),
  351. {ok, ModuleList}.
  352. load_views_inner(Application, OutDir, TranslatorPid) ->
  353. fun(File, Acc) ->
  354. TemplateAdapter = boss_files:template_adapter_for_extension(
  355. filename:extension(File)),
  356. ViewR = compile_view(Application, File, TemplateAdapter, OutDir, TranslatorPid),
  357. case ViewR of
  358. {ok, Module} ->
  359. [Module|Acc];
  360. {error, Reason} ->
  361. _ = lager:error("Unable to compile ~p because of ~p",
  362. [File, Reason]),
  363. Acc
  364. end
  365. end.
  366. load_view_if_old(Application, ViewPath, Module, OutDir, TemplateAdapter, TranslatorPid) ->
  367. case load_view_lib_if_old(Application, OutDir, TranslatorPid) of
  368. {ok, _} ->
  369. NeedCompile = case module_is_loaded(Module) of
  370. true ->
  371. Dependencies = lists:map(fun
  372. ({File, _CheckSum}) -> File;
  373. (File) -> File
  374. end, [TemplateAdapter:source(Module) | TemplateAdapter:dependencies(Module)]),
  375. TagHelpers = lists:map(fun erlang:list_to_atom/1, boss_files_util:view_tag_helper_list(Application)),
  376. FilterHelpers = lists:map(fun erlang:list_to_atom/1, boss_files_util:view_filter_helper_list(Application)),
  377. ExtraTagHelpers = boss_env:get_env(template_tag_modules, []),
  378. ExtraFilterHelpers = boss_env:get_env(template_filter_modules, []),
  379. module_older_than(Module,
  380. Dependencies ++ TagHelpers ++ FilterHelpers ++ ExtraTagHelpers ++ ExtraFilterHelpers);
  381. false ->
  382. true
  383. end,
  384. case NeedCompile of
  385. true ->
  386. compile_view(Application, ViewPath, TemplateAdapter,
  387. undefined, TranslatorPid);
  388. false ->
  389. {ok, Module}
  390. end
  391. end.
  392. load_view_if_dev(Application, ViewPath, ViewModules, TranslatorPid) ->
  393. Module = view_module(Application, ViewPath),
  394. TemplateAdapter = boss_files:template_adapter_for_extension(filename:extension(ViewPath)),
  395. case boss_env:is_developing_app(Application) of
  396. true ->
  397. OutDir = boss_files_util:ebin_dir(),
  398. case load_view_if_old(Application, ViewPath, Module, OutDir, TemplateAdapter, TranslatorPid) of
  399. {ok, Module} ->
  400. {ok, Module, TemplateAdapter};
  401. Other ->
  402. Other
  403. end;
  404. false ->
  405. case lists:member(atom_to_list(Module), ViewModules) of
  406. true ->
  407. {ok, Module, TemplateAdapter};
  408. _ ->
  409. {error, not_found}
  410. end
  411. end.
  412. module_is_loaded(Module) ->
  413. case code:is_loaded(Module) of
  414. {file, _} ->
  415. true;
  416. _ ->
  417. false
  418. end.
  419. -type maybe_list(X) :: X|list(X).
  420. -spec(module_older_than(maybe_list(module()), maybe_list(string())) ->
  421. boolean()).
  422. module_older_than(Module, Files) when is_atom(Module) ->
  423. OutDir = boss_files_util:ebin_dir(),
  424. BeamFile = filename:join([OutDir, atom_to_list(Module) ++ ".beam"]),
  425. module_older_than(BeamFile, Files);
  426. module_older_than(Module, Files) when is_list(Module) ->
  427. module_older_than(filelib:last_modified(Module), Files);
  428. module_older_than(_Date, []) ->
  429. false;
  430. module_older_than(CompileDate, [File|Rest]) when is_list(File) ->
  431. module_older_than(CompileDate, [filelib:last_modified(File)|Rest]);
  432. module_older_than(CompileDate, [Module|Rest]) when is_atom(Module) ->
  433. {file, Loaded} = code:is_loaded(Module),
  434. module_older_than(CompileDate, [Loaded|Rest]);
  435. module_older_than(CompileDate, [CompareDate|Rest]) ->
  436. (CompareDate > CompileDate) orelse module_older_than(CompileDate, Rest).
  437. view_module(Application, RelativePath) ->
  438. Components = tl(filename:split(RelativePath)),
  439. Lc = string:to_lower(lists:concat([Application, "_", string:join(Components, "_")])),
  440. ModuleIOList = re:replace(Lc, "\\.", "_", [global]),
  441. list_to_atom(binary_to_list(iolist_to_binary(ModuleIOList))).
  442. view_custom_tags_dir_module(Application) ->
  443. list_to_atom(lists:concat([Application, ?CUSTOM_TAGS_DIR_MODULE])).
  444. incoming_mail_controller_module(Application) ->
  445. list_to_atom(lists:concat([Application, "_incoming_mail_controller"])).