mad_repl.erl 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. -module(mad_repl).
  2. -copyright('Maxim Sokhatsky').
  3. -compile(export_all).
  4. disabled() -> [].
  5. system() -> [compiler,syntax_tools,sasl,tools,mnesia,reltool,xmerl,crypto,kernel,stdlib,ssh,eldap,common_test,eunit,
  6. wx,ssl,runtime_tools,public_key,observer,inets,asn1,et,eunit,hipe,os_mon,parsetools,odbc,snmp].
  7. escript_name() ->
  8. try escript:script_name() of N -> N catch _:_ -> [] end.
  9. local_app() ->
  10. case filename:basename(filelib:wildcard("ebin/*.app"),".app") of
  11. [] -> [];
  12. A -> list_to_atom(A) end.
  13. applist() ->
  14. Name = ".applist",
  15. case file:read_file(Name) of
  16. {ok,Binary} -> parse_applist(Binary);
  17. {error,_} ->
  18. case mad_repl:load_file(Name) of
  19. {error,_} -> mad_release:resolve([]);
  20. {ok,Plan} -> parse_applist(Plan) end end.
  21. wildcards(List) -> lists:concat([filelib:wildcard(X)||X<-List]).
  22. trim(Bin,Match) ->
  23. re:replace(Bin, Match, "", [{return, binary}, global]).
  24. parse_applist(AppList) ->
  25. % 20, Res = string:tokens(string:trim(binary_to_list(AppList),both,"\n []"),","),
  26. Res = string:tokens(binary_to_list(trim(AppList,"^\\s+|\\s+|\\[|\\]|\\[+|\\]+$")),","),
  27. [ list_to_atom(R) || R <-Res ] -- disabled().
  28. load_sysconfig() ->
  29. Config = wildcards(["sys.config",lists:concat(["etc/",mad:host(),"/sys.config"])]),
  30. _Apps = case Config of
  31. [] -> case mad_repl:load_file("sys.config") of
  32. {error,_} -> [];
  33. {ok,Bin} -> parse(unicode:characters_to_list(Bin)) end;
  34. File -> case file:consult(hd(File)) of
  35. {error,_} -> [];
  36. {ok,[A]} -> merge_include(A, []) end end.
  37. application_config(AppConfigs) ->
  38. [[application:set_env(App,K,V) || {K,V} <- Cfg] || {App,Cfg} <- AppConfigs].
  39. merge_include([], Acc) -> Acc;
  40. merge_include([H | Rest], Acc) -> merge_include(Rest, merge_config(H, Acc)).
  41. merge_config({App, NewConfig} = Add, Acc) ->
  42. lists:keystore(App, 1, Acc, case lists:keyfind(App, 1, Acc) of
  43. false -> Add;
  44. {App, AppConfigs} -> merge_config(App, AppConfigs, NewConfig)
  45. end);
  46. merge_config(File, Acc) when is_list(File) ->
  47. BFName = filename:basename(File, ".config"),
  48. FName = filename:join(filename:dirname(File), BFName ++ ".config"),
  49. case file:consult(FName) of
  50. {ok,[A]} -> merge_include(A, Acc);
  51. _ -> Acc
  52. end.
  53. merge_config(App, AppConfigs, []) -> {App, AppConfigs};
  54. merge_config(App, AppConfigs, [{Key, _} = Tuple | Rest]) ->
  55. merge_config(App, lists:keystore(Key, 1, AppConfigs, Tuple), Rest).
  56. acc_start(A,Acc) ->
  57. application:ensure_all_started(A), Acc.
  58. % for system application we just start, forgot about env merging
  59. load(true,A,Acc,_Config) ->
  60. acc_start(A,Acc);
  61. % for user application we should merge app from ebin and from sys.config
  62. % and start application using tuple argument in app controller
  63. load(X,A,Acc,Config) ->
  64. try {application,Name,Map} = load_config(A),
  65. NewEnv = merge(Config,Map,Name),
  66. acc_start({application,Name,set_value(env,1,Map,{env,NewEnv})},Acc)
  67. catch _:_ -> io:format("Application Load Error: ~p~n",[{X,A,Acc}]) end.
  68. merge(Config,Map,Name) ->
  69. lists:foldl(fun({Name2,E},Acc2) when Name2 =:= Name ->
  70. lists:foldl(fun({K,V},Acc1) -> set_value(K,1,Acc1,{K,V}) end,Acc2,E);
  71. (_,Acc2) -> Acc2 end, proplists:get_value(env,Map,[]), Config).
  72. load_apps([],Config,Acc) -> [ load(lists:member(A,system()),A,Acc,Config) || A <- applist()];
  73. load_apps(["applist"],Config,Acc) -> load_apps([],Config,Acc);
  74. load_apps(Params,_,_Acc) -> [ application:ensure_all_started(list_to_atom(A))||A<-Params].
  75. set_value(Name,Pos,List,New) -> add_replace(lists:keyfind(Name,Pos,List),Name,Pos,List,New).
  76. add_replace(false,_N,_P,List,New) -> [New|List];
  77. add_replace(_____,Name,Pos,List,New) -> lists:keyreplace(Name,Pos,List,New).
  78. cwd() -> case file:get_cwd() of {ok, Cwd} -> Cwd; _ -> "." end.
  79. epmd({error,{{shutdown, {_,net_kernel,{'EXIT',nodistribution}}},_}}) ->
  80. mad:info("Erlang Distribution failed, falling back to nonode@nohost. Verify that epmd is running and try again.",[]);
  81. epmd(_) -> ok.
  82. sh(["-sname", Name| Rest]) -> epmd(net_kernel:start([list_to_atom(Name), shortnames])),sh(Rest);
  83. sh(["-name" , Name| Rest]) -> epmd(net_kernel:start([list_to_atom(Name), longnames ])),sh(Rest);
  84. sh(Params) ->
  85. case mad_utils:configs() of
  86. {error,E} -> {error,E};
  87. {ok,{ _Cwd,_ConfigFileName,_Config }} ->
  88. SystemPath = filelib:wildcard(code:root_dir() ++ "/lib/{"
  89. ++ string:join([atom_to_list(X)||X<-mad_repl:system()],",") ++ "}-*/ebin"),
  90. UserPath = wildcards(["{apps,deps}/*/ebin","ebin"]),
  91. code:set_path(SystemPath++UserPath),
  92. case escript_name() of
  93. [] -> ok; % VSCode
  94. N -> code:add_path(filename:join([cwd(),filename:basename(N)])),
  95. load()
  96. end,
  97. Config = load_sysconfig(),
  98. application_config(Config),
  99. Driver = mad_utils:get_value(shell_driver,_Config,user_drv),
  100. Logger = maybe_remove_logger(),
  101. repl_intro(Config),
  102. case os:type() of
  103. {win32,nt} -> shell:start();
  104. _ -> O = whereis(user),
  105. supervisor:terminate_child(kernel_sup, user),
  106. Driver:start(),
  107. wait(3000),
  108. rewrite_leaders(O,whereis(user)) end,
  109. maybe_reset_logger(Logger),
  110. load_apps(Params,Config,[]),
  111. case Params of
  112. ["applist"] -> skip;
  113. _ -> timer:sleep(infinity) end end.
  114. %% credits: https://github.com/erlang/rebar3/commit/1b6e3ac9c0e3b77215b119427b9a4d530cb08e1d#diff-e76e94656cd6ae25cfb09092fda3091e
  115. maybe_remove_logger() ->
  116. case erlang:function_exported(logger, module_info, 0) of
  117. false -> ignore;
  118. true -> {ok, Cfg} = logger:get_handler_config(default),
  119. logger:remove_handler(default),
  120. {restart, Cfg} end.
  121. maybe_reset_logger(ignore) -> ok;
  122. maybe_reset_logger({restart, #{module := Mod, config := Cfg, filters := Flt, formatter := Fmt}}) ->
  123. logger:add_handler(default, Mod, Cfg),
  124. logger:set_handler_config(default, filters, Flt),
  125. logger:set_handler_config(default, formatter, Fmt).
  126. remove(0) -> skip;
  127. remove(N) -> case gen_event:delete_handler(error_logger, error_logger, []) of
  128. {error, module_not_found} -> ok;
  129. {error_logger, _} -> remove(N-1) end.
  130. wait(0) -> erlang:error(timeout);
  131. wait(Timeout) -> case whereis(user) of undefined -> timer:sleep(100), wait(Timeout - 100); _ -> ok end.
  132. rewrite_leaders(OldUser, NewUser) ->
  133. _ = [catch erlang:group_leader(NewUser, Pid)
  134. || Pid <- erlang:processes(),
  135. proplists:get_value(group_leader, erlang:process_info(Pid)) == OldUser,
  136. is_process_alive(Pid)],
  137. OldMasters = [Pid
  138. || Pid <- erlang:processes(),
  139. Pid < NewUser, % only change old masters
  140. {_,Dict} <- [erlang:process_info(Pid, dictionary)],
  141. {application_master,init,4} == proplists:get_value('$initial_call', Dict)],
  142. _ = [catch erlang:group_leader(NewUser, Pid)
  143. || Pid <- erlang:processes(),
  144. lists:member(proplists:get_value(group_leader, erlang:process_info(Pid)),
  145. OldMasters)],
  146. try error_logger:swap_handler(tty),
  147. remove(3)
  148. catch _E:_R -> hope_for_best end.
  149. load() ->
  150. ets_created(),
  151. {ok,Sections} = escript:extract(escript:script_name(),[]),
  152. [Bin] = [B||{archive,B}<-Sections],
  153. unfold_zips(Bin).
  154. unfold_zips(Bin) ->
  155. {ok,Unzip} = zip:unzip(Bin,[memory]),
  156. [ begin
  157. try
  158. Path = binary_to_list(base64:decode(list_to_binary(U))),
  159. ets:insert(filesystem,{unicode:characters_to_list(Path),FileBin}),
  160. case Path of
  161. "deps/n2o/priv/" ++ _X ->
  162. filelib:ensure_dir(filename:dirname(Path)++"/"),
  163. file:write_file(Path,FileBin);
  164. "priv/static/" ++ _X ->
  165. filelib:ensure_dir(filename:dirname(Path)++"/"),
  166. file:write_file(Path,FileBin);
  167. _ -> ok
  168. end
  169. catch _:_ ->
  170. ets:insert(filesystem,{U,FileBin})
  171. end,
  172. case U of
  173. "static.gz" -> unfold_zips(FileBin);
  174. _ -> skip end
  175. end || {U,FileBin} <- Unzip].
  176. ets_created() ->
  177. case ets:info(filesystem) of
  178. undefined -> ets:new(filesystem,[set,named_table,{keypos,1},public]);
  179. _ -> skip end.
  180. load_file(Name) ->
  181. ets_created(),
  182. case ets:lookup(filesystem,Name) of
  183. [{Name,Bin}] -> {ok,Bin};
  184. _ -> {error,etsfs} end.
  185. load_config(A) when is_atom(A) -> load_config(atom_to_list(A));
  186. load_config(A) when is_list(A) ->
  187. AppFile = A ++".app",
  188. Name = wildcards(["{apps,deps}/*/ebin/"++AppFile,"ebin/"++AppFile]),
  189. case file:read_file(Name) of
  190. {ok,Bin} -> parse(binary_to_list(Bin));
  191. {error,_} -> case ets:lookup(filesystem,AppFile) of
  192. [{AppFile,Bin}] -> parse(binary_to_list(Bin));
  193. _ -> [] end end.
  194. parse(String) ->
  195. {ok,Tokens,_EndLine} = erl_scan:string(String),
  196. {ok,AbsForm} = erl_parse:parse_exprs(Tokens),
  197. {value,Value,_Bs} = erl_eval:exprs(AbsForm, erl_eval:new_bindings()),
  198. Value.
  199. repl_intro(Config) ->
  200. io:format("Configuration: ~p~n", [Config]),
  201. io:format("Applications: ~p~n", [applist()]).