-module(eSync).
|
|
|
|
-behaviour(es_gen_ipc).
|
|
|
|
-include_lib("kernel/include/file.hrl").
|
|
|
|
-compile(inline).
|
|
-compile({inline_size, 128}).
|
|
|
|
-define(CASE(Cond, Ret1, Ret2), (case Cond of true -> Ret1; _ -> Ret2 end)).
|
|
-define(LOG_ON(Val), Val == true; Val == all; Val == skip_success; is_list(Val), Val =/= []).
|
|
|
|
-define(Log, log).
|
|
-define(baseDir, baseDir).
|
|
-define(monitorExt, monitorExt).
|
|
-define(compileCmd, compileCmd).
|
|
-define(extraDirs, extraDirs).
|
|
-define(descendant, descendant).
|
|
-define(onMSyncFun, onMSyncFun).
|
|
-define(onCSyncFun, onCSyncFun).
|
|
-define(swSyncNode, swSyncNode).
|
|
-define(isJustMem, isJustMem).
|
|
-define(debugInfoKeyFun, debugInfoKeyFun).
|
|
|
|
-define(ExtList, [".hrl", ".erl", ".beam", ".dtl", ".lfe", ".ex", ".config"]).
|
|
|
|
-define(DefCfgList, [{?Log, all}, {?baseDir, "./"}, {?monitorExt, ?ExtList}, {?compileCmd, undefined}, {?extraDirs, undefined}, {?descendant, fix}, {?onMSyncFun, undefined}, {?onCSyncFun, undefined}, {?swSyncNode, false}, {?isJustMem, false}, {?debugInfoKeyFun, undefined}]).
|
|
|
|
-define(esCfgSync, esCfgSync).
|
|
-define(rootSrcDir, <<"src">>).
|
|
|
|
-define(logSuccess(Format), canLog(success) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE])).
|
|
-define(logSuccess(Format, Args), canLog(success) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE] ++ Args)).
|
|
|
|
-define(logErrors(Format), canLog(errors) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE])).
|
|
-define(logErrors(Format, Args), canLog(errors) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE] ++ Args)).
|
|
|
|
-define(logWarnings(Format), canLog(warnings) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE])).
|
|
-define(logWarnings(Format, Args), canLog(warnings) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE] ++ Args)).
|
|
|
|
-export([
|
|
start/2,
|
|
start/0,
|
|
stop/0,
|
|
run/0
|
|
]).
|
|
|
|
%% API
|
|
-export([
|
|
start_link/0,
|
|
rescan/0,
|
|
pause/0,
|
|
unpause/0,
|
|
setLog/1,
|
|
getLog/0,
|
|
curInfo/0,
|
|
getOnMSync/0,
|
|
setOnMSync/1,
|
|
getOnCSync/0,
|
|
setOnCSync/1,
|
|
swSyncNode/1
|
|
]).
|
|
|
|
%% es_gen_ipc callbacks
|
|
-export([
|
|
init/1,
|
|
handleCall/4,
|
|
handleAfter/3,
|
|
handleCast/3,
|
|
handleInfo/3,
|
|
handleOnevent/4,
|
|
terminate/3
|
|
]).
|
|
|
|
start(_StartType, _StartArgs) ->
|
|
start_link().
|
|
|
|
start() ->
|
|
application:ensure_all_started(eSync).
|
|
|
|
stop() ->
|
|
application:stop(eSync).
|
|
|
|
run() ->
|
|
case start() of
|
|
{ok, _Started} ->
|
|
unpause(),
|
|
ok;
|
|
{error, Reason} ->
|
|
?logErrors("start eSync error:~p~n", [Reason])
|
|
end.
|
|
|
|
-define(SERVER, ?MODULE).
|
|
-define(None, 0).
|
|
|
|
-record(state, {
|
|
port = undefined
|
|
, onMSyncFun = undefined
|
|
, onCSyncFun = undefined
|
|
, swSyncNode = false
|
|
, srcFiles = #{} :: map()
|
|
, hrlFiles = #{} :: map()
|
|
, configs = #{} :: map()
|
|
, beams = #{} :: map()
|
|
}).
|
|
|
|
%% ************************************ API start ***************************
|
|
rescan() ->
|
|
es_gen_ipc:cast(?SERVER, miRescan),
|
|
?logSuccess("start rescaning source files..."),
|
|
ok.
|
|
|
|
unpause() ->
|
|
es_gen_ipc:cast(?SERVER, miUnpause),
|
|
ok.
|
|
|
|
pause() ->
|
|
es_gen_ipc:cast(?SERVER, miPause),
|
|
?logSuccess("Pausing eSync. Call eSync:run() to restart"),
|
|
ok.
|
|
|
|
curInfo() ->
|
|
es_gen_ipc:call(?SERVER, miCurInfo).
|
|
|
|
setLog(T) when ?LOG_ON(T) ->
|
|
setEnv(log, T),
|
|
loadCfg(),
|
|
?logSuccess("Console Notifications Enabled"),
|
|
ok;
|
|
setLog(_) ->
|
|
setEnv(log, none),
|
|
loadCfg(),
|
|
?logSuccess("Console Notifications Disabled"),
|
|
ok.
|
|
|
|
getLog() ->
|
|
?esCfgSync:getv(log).
|
|
|
|
swSyncNode(IsSync) ->
|
|
es_gen_ipc:cast(?SERVER, {miSyncNode, IsSync}),
|
|
ok.
|
|
|
|
getOnMSync() ->
|
|
es_gen_ipc:call(?SERVER, miGetOnMSync).
|
|
|
|
setOnMSync(Fun) ->
|
|
es_gen_ipc:call(?SERVER, {miSetOnMSync, Fun}).
|
|
|
|
getOnCSync() ->
|
|
es_gen_ipc:call(?SERVER, miGetOnCSync).
|
|
|
|
setOnCSync(Fun) ->
|
|
es_gen_ipc:call(?SERVER, {miSetOnCSync, Fun}).
|
|
|
|
%% ************************************ API end ***************************
|
|
start_link() ->
|
|
es_gen_ipc:start_link({local, ?SERVER}, ?MODULE, ?None, []).
|
|
|
|
%% status :: waiting | running | pause
|
|
init(_Args) ->
|
|
erlang:process_flag(trap_exit, true),
|
|
loadCfg(),
|
|
{ok, waiting, #state{onMSyncFun = ?esCfgSync:getv(?onMSyncFun), onCSyncFun = ?esCfgSync:getv(?onCSyncFun), swSyncNode = ?esCfgSync:getv(?swSyncNode)}, {doAfter, ?None}}.
|
|
|
|
handleAfter(?None, waiting, State) ->
|
|
%% 启动port 发送监听目录信息
|
|
{AddExtraSrcDirs, AddOnlySrcDirs, OnlySrcDirs, DelSrcDirs} = mergeExtraDirs(false),
|
|
AddExtraStr = string:join([filename:nativename(OneDir) || OneDir <- AddExtraSrcDirs], "|"),
|
|
AddOnlyStr = string:join([filename:nativename(OneDir) || OneDir <- AddOnlySrcDirs], "|"),
|
|
OnlyStr = string:join([filename:nativename(OneDir) || OneDir <- OnlySrcDirs], "|"),
|
|
DelStr = string:join([filename:nativename(OneDir) || OneDir <- DelSrcDirs], "|"),
|
|
AllStr = string:join([AddExtraStr, AddOnlyStr, OnlyStr, DelStr], "\r\n"),
|
|
|
|
BaseDirStr = filename:nativename(?esCfgSync:getv(?baseDir)),
|
|
MonitorExtStr = string:join(?esCfgSync:getv(?monitorExt), "|"),
|
|
|
|
Opts = [{packet, 4}, binary, exit_status, use_stdio, {args, [BaseDirStr, MonitorExtStr, AllStr]}],
|
|
PortName = fileSyncPath("fileSync"),
|
|
Port = erlang:open_port({spawn_executable, PortName}, Opts),
|
|
{kpS, State#state{port = Port}, {sTimeout, 4000, waitConnOver}}.
|
|
|
|
handleCall(miGetOnMSync, _, #state{onMSyncFun = OnMSyncFun} = State, _From) ->
|
|
{reply, OnMSyncFun, State};
|
|
handleCall({miSetOnMSync, Fun}, _, State, _From) ->
|
|
{reply, ok, State#state{onMSyncFun = Fun}};
|
|
handleCall(miGetOnCSync, _, #state{onCSyncFun = OnCSyncFun} = State, _From) ->
|
|
{reply, OnCSyncFun, State};
|
|
handleCall({miSetOnCSync, Fun}, _, State, _From) ->
|
|
{reply, ok, State#state{onCSyncFun = Fun}};
|
|
handleCall(miCurInfo, Status, State, _Form) ->
|
|
{reply, {Status, erlang:get(), State}, State};
|
|
handleCall(_Request, _, _State, _From) ->
|
|
kpS_S.
|
|
|
|
handleCast(miPause, running, State) ->
|
|
{nextS, pause, State};
|
|
handleCast(miUnpause, pause, State) ->
|
|
{nextS, running, State};
|
|
handleCast({miSyncNode, IsSync}, _, State) ->
|
|
case IsSync of
|
|
true ->
|
|
{kpS, State#state{swSyncNode = true}};
|
|
_ ->
|
|
{kpS, State#state{swSyncNode = false}}
|
|
end;
|
|
handleCast(miRescan, _, State) ->
|
|
{Srcs, Hrls, Configs, Beams} = collSrcFiles(false),
|
|
{noreply, State#state{srcFiles = Srcs, hrlFiles = Hrls, configs = Configs, beams = Beams}, hibernate};
|
|
handleCast(_Msg, _, _State) ->
|
|
kpS_S.
|
|
|
|
handleInfo({_Port, {data, Data}}, Status, #state{srcFiles = Srcs, hrlFiles = Hrls, configs = Configs, beams = Beams, onMSyncFun = OnMSyncFun, onCSyncFun = OnCSyncFun, swSyncNode = SwSyncNode} = State) ->
|
|
case Status of
|
|
running ->
|
|
FileList = binary:split(Data, <<"\r\n">>, [global]),
|
|
%% 收集改动了beam hrl src 文件 然后执行相应的逻辑
|
|
{CBeams, CConfigs, CHrls, CSrcs, NewSrcs, NewHrls, NewConfigs, NewBeams} = classifyChangeFile(FileList, [], [], #{}, #{}, Srcs, Hrls, Configs, Beams),
|
|
fireOnSync(OnCSyncFun, CConfigs),
|
|
reloadChangedMod(CBeams, SwSyncNode, OnMSyncFun, []),
|
|
case ?esCfgSync:getv(?compileCmd) of
|
|
undefined ->
|
|
LastCHrls = collIncludeCHrls(maps:keys(CHrls), NewHrls, CHrls, #{}),
|
|
NReSrcs = collIncludeCErls(maps:keys(LastCHrls), NewSrcs, CSrcs, #{}),
|
|
recompileChangeSrcFile(maps:iterator(NReSrcs), SwSyncNode),
|
|
{kpS, State#state{srcFiles = NewSrcs, hrlFiles = NewHrls, configs = NewConfigs, beams = NewBeams}};
|
|
CmdStr ->
|
|
case maps:size(CSrcs) > 0 orelse CHrls =/= [] of
|
|
true ->
|
|
RetStr = os:cmd(CmdStr),
|
|
RetList = string:split(RetStr, "\n", all),
|
|
?logSuccess("compile cmd:~p ~n", [CmdStr]),
|
|
?logSuccess("the result: ~n ", []),
|
|
[
|
|
begin
|
|
?logSuccess("~p ~n", [OneRet])
|
|
end || OneRet <- RetList, OneRet =/= []
|
|
],
|
|
ok;
|
|
_ ->
|
|
ignore
|
|
end,
|
|
kpS_S
|
|
end;
|
|
_ ->
|
|
case Data of
|
|
<<"init">> ->
|
|
%% 然后收集一下监听目录下的src文件
|
|
{BSrcs, BHrls, BConfigs, BBeams} = collSrcFiles(true),
|
|
?logSuccess("eSync connect fileSync success and coll src files over..."),
|
|
{nextS, running, State#state{srcFiles = BSrcs, hrlFiles = BHrls, configs = BConfigs, beams = BBeams}, {isHib, true}};
|
|
_ ->
|
|
?logErrors("error, receive unexpect port msg ~p~n", [Data]),
|
|
kpS_S
|
|
end
|
|
end;
|
|
handleInfo({Port, closed}, running, #state{port = Port} = _State) ->
|
|
?logErrors("receive port closed ~n"),
|
|
{nextS, port_close, _State};
|
|
handleInfo({'EXIT', Port, Reason}, running, #state{port = Port} = _State) ->
|
|
?logErrors("receive port exit Reason:~p ~n", [Reason]),
|
|
{nextS, {port_EXIT, Reason}, _State};
|
|
handleInfo({Port, {exit_status, Status}}, running, #state{port = Port} = _State) ->
|
|
?logErrors("receive port exit_status Status:~p ~p ~n", [Status, Port]),
|
|
{nextS, {port_exit_status, Status}, _State};
|
|
handleInfo({'EXIT', _Pid, _Reason}, running, _State) ->
|
|
kpS_S;
|
|
handleInfo(_Msg, _, _State) ->
|
|
?logErrors("receive unexpect msg:~p ~n", [_Msg]),
|
|
kpS_S.
|
|
|
|
handleOnevent(sTimeout, waitConnOver, Status, State) ->
|
|
?logErrors("failed to connect the fileSync to stop stauts:~p state:~p ~n", [Status, State]),
|
|
stop;
|
|
handleOnevent(_EventType, _EventContent, _Status, _State) ->
|
|
kpS_S.
|
|
|
|
terminate(_Reason, _Status, _State) ->
|
|
ok.
|
|
|
|
%% ************************************************* utils start *******************************************************
|
|
getModSrcDir(Module) ->
|
|
case code:is_loaded(Module) of
|
|
{file, _} ->
|
|
try
|
|
%% Get some module info...
|
|
Props = Module:module_info(compile),
|
|
Source = proplists:get_value(source, Props, ""),
|
|
%% Ensure that the file exists, is a decendent of the tree, and how to deal with that
|
|
IsFile = filelib:is_regular(Source),
|
|
IsDescendant = isDescendent(Source),
|
|
Descendant = ?esCfgSync:getv(?descendant),
|
|
LastSource =
|
|
case {IsFile, IsDescendant, Descendant} of
|
|
%% is file and descendant, we're good to go
|
|
{true, true, _} -> Source;
|
|
%% is not a descendant, but we allow them, so good to go
|
|
{true, false, allow} -> Source;
|
|
%% is not a descendant, and we fix non-descendants, so let's fix it
|
|
{_, false, fix} -> fixDescendantSource(Source, IsFile);
|
|
%% Anything else, and we don't know what to do, so let's just bail.
|
|
_ -> undefined
|
|
end,
|
|
case LastSource of
|
|
undefined ->
|
|
undefined;
|
|
_ ->
|
|
%% Get the source dir...
|
|
Dir = filename:dirname(LastSource),
|
|
getSrcDir(Dir)
|
|
end
|
|
catch _ : _ ->
|
|
undefined
|
|
end;
|
|
_ ->
|
|
undefined
|
|
end.
|
|
|
|
getModOpts(Module) ->
|
|
case code:is_loaded(Module) of
|
|
{file, _} ->
|
|
try
|
|
Props = Module:module_info(compile),
|
|
BeamDir = filename:dirname(code:which(Module)),
|
|
Options1 = proplists:get_value(options, Props, []),
|
|
%% transform `outdir'
|
|
Options2 = transformOutdir(BeamDir, Options1),
|
|
Options3 = ensureInclude(Options2),
|
|
%% transform the include directories
|
|
Options4 = transformAllIncludes(Module, BeamDir, Options3),
|
|
%% maybe_add_compile_info
|
|
Options5 = maybeAddCompileInfo(Options4),
|
|
%% add filetype to options (DTL, LFE, erl, etc)
|
|
Options6 = addFileType(Module, Options5),
|
|
Options7 = lists:keyreplace(debug_info_key, 1, Options6, debugInfoKeyFun()),
|
|
{ok, Options7}
|
|
catch ExType:Error ->
|
|
?logWarnings("~p:0: ~p looking for options: ~p. ~n", [Module, ExType, Error]),
|
|
undefined
|
|
end;
|
|
_ ->
|
|
undefined
|
|
end.
|
|
|
|
tryGetModOpts(Module) ->
|
|
try
|
|
Props = Module:module_info(compile),
|
|
BeamDir = filename:dirname(code:which(Module)),
|
|
Options1 = proplists:get_value(options, Props, []),
|
|
%% transform `outdir'
|
|
Options2 = transformOutdir(BeamDir, Options1),
|
|
Options3 = ensureInclude(Options2),
|
|
%% transform the include directories
|
|
Options4 = transformAllIncludes(Module, BeamDir, Options3),
|
|
%% maybe_add_compile_info
|
|
Options5 = maybeAddCompileInfo(Options4),
|
|
%% add filetype to options (DTL, LFE, erl, etc)
|
|
Options6 = addFileType(Module, Options5),
|
|
Options7 = lists:keyreplace(debug_info_key, 1, Options6, debugInfoKeyFun()),
|
|
{ok, Options7}
|
|
catch _ExType:_Error ->
|
|
undefined
|
|
end.
|
|
|
|
tryGetSrcOpts(SrcDir) ->
|
|
%% Then we dig back through the parent directories until we find our include directory
|
|
NewDirName = filename:dirname(SrcDir),
|
|
case getOptions(NewDirName) of
|
|
{ok, _Options} = Opts ->
|
|
Opts;
|
|
_ ->
|
|
BaseName = filename:basename(SrcDir),
|
|
IsBaseSrcDir = BaseName == ?rootSrcDir,
|
|
case NewDirName =/= SrcDir andalso not IsBaseSrcDir of
|
|
true ->
|
|
tryGetSrcOpts(NewDirName);
|
|
_ when IsBaseSrcDir ->
|
|
try filelib:fold_files(SrcDir, ".*\\.(erl|dtl|lfe|ex)$", true,
|
|
fun(OneFile, Acc) ->
|
|
Mod = binary_to_atom(filename:basename(OneFile, filename:extension(OneFile))),
|
|
case tryGetModOpts(Mod) of
|
|
{ok, _Options} = Opts ->
|
|
throw(Opts);
|
|
_ ->
|
|
Acc
|
|
end
|
|
end, undefined)
|
|
catch
|
|
{ok, _Options} = Opts ->
|
|
Opts;
|
|
_ExType:_Error ->
|
|
?logWarnings("looking src options error ~p:~p. ~n", [_ExType, _Error]),
|
|
undefined
|
|
end;
|
|
_ ->
|
|
undefined
|
|
end
|
|
end.
|
|
|
|
transformOutdir(BeamDir, Options) ->
|
|
[{outdir, BeamDir} | proplists:delete(outdir, Options)].
|
|
|
|
ensureInclude(Options) ->
|
|
case proplists:get_value(i, Options) of
|
|
undefined -> [{i, "include"} | Options];
|
|
_ -> Options
|
|
end.
|
|
|
|
transformAllIncludes(Module, BeamDir, Options) ->
|
|
[begin
|
|
case Opt of
|
|
{i, IncludeDir} ->
|
|
{ok, SrcDir} = getModSrcDir(Module),
|
|
{ok, IncludeDir2} = determineIncludeDir(IncludeDir, BeamDir, SrcDir),
|
|
{i, IncludeDir2};
|
|
_ ->
|
|
Opt
|
|
end
|
|
end || Opt <- Options].
|
|
|
|
maybeAddCompileInfo(Options) ->
|
|
case lists:member(compile_info, Options) of
|
|
true -> Options;
|
|
false -> addCompileInfo(Options)
|
|
end.
|
|
|
|
addCompileInfo(Options) ->
|
|
CompInfo = [{K, V} || {K, V} <- Options, lists:member(K, [outdir, i])],
|
|
[{compile_info, CompInfo} | Options].
|
|
|
|
addFileType(Module, Options) ->
|
|
Type = getFileType(Module),
|
|
[{type, Type} | Options].
|
|
|
|
%% This will check if the given module or source file is an ErlyDTL template.
|
|
%% Currently, this is done by checking if its reported source path ends with
|
|
%% ".dtl.erl".
|
|
getFileType(Module) when is_atom(Module) ->
|
|
Props = Module:module_info(compile),
|
|
Source = proplists:get_value(source, Props, ""),
|
|
getFileType(Source);
|
|
|
|
getFileType(Source) ->
|
|
Ext = filename:extension(Source),
|
|
Root = filename:rootname(Source),
|
|
SecondExt = filename:extension(Root),
|
|
case Ext of
|
|
<<".erl">> when SecondExt =:= <<".dtl">> -> dtl;
|
|
<<".dtl">> -> dtl;
|
|
<<".erl">> -> erl;
|
|
<<".lfe">> -> lfe;
|
|
<<".ex">> -> elixir;
|
|
".erl" when SecondExt =:= ".dtl" -> dtl;
|
|
".dtl" -> dtl;
|
|
".erl" -> erl;
|
|
".lfe" -> lfe;
|
|
".ex" -> elixir
|
|
end.
|
|
|
|
%% This will search back to find an appropriate include directory, by
|
|
%% searching further back than "..". Instead, it will extract the basename
|
|
%% (probably "include" from the include pathfile, and then search backwards in
|
|
%% the directory tree until it finds a directory with the same basename found
|
|
%% above.
|
|
determineIncludeDir(IncludeDir, BeamDir, SrcDir) ->
|
|
IncludeBase = filename:basename(IncludeDir),
|
|
case determineIncludeDirFromBeamDir(IncludeBase, IncludeDir, BeamDir) of
|
|
{ok, _Dir} = RetD -> RetD;
|
|
undefined ->
|
|
{ok, Cwd} = file:get_cwd(),
|
|
% Cwd2 = normalizeCaseWindowsDir(Cwd),
|
|
% SrcDir2 = normalizeCaseWindowsDir(SrcDir),
|
|
% IncludeBase2 = normalizeCaseWindowsDir(IncludeBase),
|
|
case findIncludeDirFromAncestors(SrcDir, Cwd, IncludeBase) of
|
|
{ok, _Dir} = RetD -> RetD;
|
|
undefined -> {ok, IncludeDir} %% Failed, just stick with original
|
|
end
|
|
end.
|
|
|
|
%% First try to see if we have an include file alongside our ebin directory, which is typically the case
|
|
determineIncludeDirFromBeamDir(IncludeBase, IncludeDir, BeamDir) ->
|
|
BeamBasedIncDir = filename:join(filename:dirname(BeamDir), IncludeBase),
|
|
case filelib:is_dir(BeamBasedIncDir) of
|
|
true -> {ok, BeamBasedIncDir};
|
|
false ->
|
|
BeamBasedIncDir2 = filename:join(filename:dirname(BeamDir), IncludeDir),
|
|
case filelib:is_dir(BeamBasedIncDir2) of
|
|
true -> {ok, BeamBasedIncDir2};
|
|
_ ->
|
|
undefined
|
|
end
|
|
end.
|
|
|
|
%% get the src dir
|
|
getRootSrcDirFromSrcDir(SrcDir) ->
|
|
NewDirName = filename:dirname(SrcDir),
|
|
BaseName = filename:basename(SrcDir),
|
|
case BaseName of
|
|
?rootSrcDir ->
|
|
NewDirName;
|
|
_ ->
|
|
case NewDirName =/= SrcDir of
|
|
true ->
|
|
getRootSrcDirFromSrcDir(NewDirName);
|
|
_ ->
|
|
undefined
|
|
end
|
|
end.
|
|
|
|
%% Then we dig back through the parent directories until we find our include directory
|
|
findIncludeDirFromAncestors(Cwd, Cwd, _) -> undefined;
|
|
findIncludeDirFromAncestors("/", _, _) -> undefined;
|
|
findIncludeDirFromAncestors(".", _, _) -> undefined;
|
|
findIncludeDirFromAncestors("", _, _) -> undefined;
|
|
findIncludeDirFromAncestors(Dir, Cwd, IncludeBase) ->
|
|
NewDirName = filename:dirname(Dir),
|
|
AttemptDir = filename:join(NewDirName, IncludeBase),
|
|
case filelib:is_dir(AttemptDir) of
|
|
true ->
|
|
{ok, AttemptDir};
|
|
false ->
|
|
case NewDirName =/= Dir of
|
|
true ->
|
|
findIncludeDirFromAncestors(NewDirName, Cwd, IncludeBase);
|
|
_ ->
|
|
undefined
|
|
end
|
|
end.
|
|
|
|
% normalizeCaseWindowsDir(Dir) ->
|
|
% case os:type() of
|
|
% {win32, _} -> Dir; %string:to_lower(Dir);
|
|
% {unix, _} -> Dir
|
|
% end.
|
|
|
|
%% This is an attempt to intelligently fix paths in modules when a
|
|
%% release is moved. Essentially, it takes a module name and its original path
|
|
%% from Module:module_info(compile), say
|
|
%% "/some/original/path/site/src/pages/somepage.erl", and then breaks down the
|
|
%% path one by one prefixing it with the current working directory until it
|
|
%% either finds a match, or fails. If it succeeds, it returns the Path to the
|
|
%% new Source file.
|
|
fixDescendantSource([], _IsFile) ->
|
|
undefined;
|
|
fixDescendantSource(Path, IsFile) ->
|
|
{ok, Cwd} = file:get_cwd(),
|
|
PathParts = filename:split(Path),
|
|
case makeDescendantSource(PathParts, Cwd) of
|
|
undefined -> case IsFile of true -> Path; _ -> undefined end;
|
|
FoundPath -> FoundPath
|
|
end.
|
|
|
|
makeDescendantSource([], _Cwd) ->
|
|
undefined;
|
|
makeDescendantSource([_ | T], Cwd) ->
|
|
PathAttempt = filename:join([Cwd | T]),
|
|
case filelib:is_regular(PathAttempt) of
|
|
true -> PathAttempt;
|
|
false -> makeDescendantSource(T, Cwd)
|
|
end.
|
|
|
|
isDescendent(Path) ->
|
|
{ok, Cwd} = file:get_cwd(),
|
|
lists:sublist(Path, length(Cwd)) == Cwd.
|
|
|
|
%% @private Find the src directory for the specified Directory; max 15 iterations
|
|
getSrcDir(Dir) ->
|
|
getSrcDir(Dir, 15).
|
|
|
|
getSrcDir(_Dir, 0) ->
|
|
undefined;
|
|
getSrcDir(Dir, Ctr) ->
|
|
HasCode = filelib:wildcard("*.{erl,hrl,ex,dtl,lfe}", Dir) /= [],
|
|
if
|
|
HasCode -> {ok, Dir};
|
|
true -> getSrcDir(filename:dirname(Dir), Ctr - 1)
|
|
end.
|
|
|
|
mergeExtraDirs(IsAddPath) ->
|
|
case ?esCfgSync:getv(?extraDirs) of
|
|
undefined ->
|
|
{[], [], [], []};
|
|
ExtraList ->
|
|
FunMerge =
|
|
fun(OneExtra, {AddExtraDirs, AddOnlyDirs, OnlyDirs, DelDirs} = AllAcc) ->
|
|
case OneExtra of
|
|
{addExtra, DirsAndOpts} ->
|
|
Adds =
|
|
[
|
|
begin
|
|
case IsAddPath of
|
|
true ->
|
|
case proplists:get_value(outdir, Opts) of
|
|
undefined ->
|
|
true;
|
|
Path ->
|
|
ok = filelib:ensure_dir(Path),
|
|
true = code:add_pathz(Path)
|
|
end;
|
|
_ ->
|
|
ignore
|
|
end,
|
|
filename:absname(Dir)
|
|
end || {Dir, Opts} <- DirsAndOpts
|
|
],
|
|
setelement(1, AllAcc, Adds ++ AddExtraDirs);
|
|
{addOnly, DirsAndOpts} ->
|
|
Adds =
|
|
[
|
|
begin
|
|
case IsAddPath of
|
|
true ->
|
|
case proplists:get_value(outdir, Opts) of
|
|
undefined ->
|
|
true;
|
|
Path ->
|
|
ok = filelib:ensure_dir(Path),
|
|
true = code:add_pathz(Path)
|
|
end;
|
|
_ ->
|
|
ignore
|
|
end,
|
|
filename:absname(Dir)
|
|
end || {Dir, Opts} <- DirsAndOpts
|
|
],
|
|
setelement(2, AllAcc, Adds ++ AddOnlyDirs);
|
|
{only, DirsAndOpts} ->
|
|
Onlys =
|
|
[
|
|
begin
|
|
case IsAddPath of
|
|
true ->
|
|
case proplists:get_value(outdir, Opts) of
|
|
undefined ->
|
|
true;
|
|
Path ->
|
|
ok = filelib:ensure_dir(Path),
|
|
true = code:add_pathz(Path)
|
|
end;
|
|
_ ->
|
|
ignore
|
|
end,
|
|
filename:absname(Dir)
|
|
end || {Dir, Opts} <- DirsAndOpts
|
|
],
|
|
setelement(3, AllAcc, Onlys ++ OnlyDirs);
|
|
{del, DirsAndOpts} ->
|
|
Dels =
|
|
[
|
|
begin
|
|
filename:absname(Dir)
|
|
end || {Dir, _Opts} <- DirsAndOpts
|
|
],
|
|
setelement(4, AllAcc, Dels ++ DelDirs)
|
|
end
|
|
end,
|
|
lists:foldl(FunMerge, {[], [], [], []}, ExtraList)
|
|
end.
|
|
|
|
toBinary(Value) when is_list(Value) -> list_to_binary(Value);
|
|
toBinary(Value) when is_binary(Value) -> Value.
|
|
|
|
regExp() ->
|
|
% <<".*\\.(erl|hrl|beam|config|dtl|lfe|ex)$">>
|
|
<<_Del:8, RegExpStr/binary>> = << <<"|", (toBinary(Tail))/binary>> || [_Dot | Tail] <- ?esCfgSync:getv(?monitorExt)>>,
|
|
<<".*\\.(", RegExpStr/binary, ")$">>.
|
|
|
|
-define(RegExp, regExp()).
|
|
collSrcFiles(IsAddPath) ->
|
|
{AddExtraSrcDirs, AddOnlySrcDirs, OnlySrcDirs, DelSrcDirs} = mergeExtraDirs(IsAddPath),
|
|
CollFiles = filelib:fold_files(filename:absname(toBinary(?esCfgSync:getv(?baseDir))), ?RegExp, true,
|
|
fun(OneFile, {Srcs, Hrls, Configs, Beams} = Acc) ->
|
|
case isOnlyDir(OnlySrcDirs, OneFile) andalso (not isDelDir(DelSrcDirs, OneFile)) of
|
|
true ->
|
|
MTimeSec = case file:read_file_info(OneFile, [raw]) of
|
|
{ok, FileInfo} ->
|
|
dateTimeToSec(FileInfo#file_info.mtime);
|
|
_ ->
|
|
0
|
|
end,
|
|
%MTimeSec = dateTimeToSec(filelib:last_modified(OneFile)),
|
|
case filename:extension(OneFile) of
|
|
<<".beam">> ->
|
|
BeamMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)),
|
|
setelement(4, Acc, Beams#{BeamMod => MTimeSec});
|
|
<<".config">> ->
|
|
setelement(3, Acc, Configs#{OneFile => MTimeSec});
|
|
<<".hrl">> ->
|
|
setelement(2, Acc, Hrls#{OneFile => MTimeSec});
|
|
<<>> ->
|
|
Acc;
|
|
_ ->
|
|
RootSrcDir =
|
|
case getRootSrcDirFromSrcDir(OneFile) of
|
|
undefined ->
|
|
filename:dirname(OneFile);
|
|
RetSrcDir ->
|
|
RetSrcDir
|
|
end,
|
|
case getOptions(RootSrcDir) of
|
|
undefined ->
|
|
Mod = binary_to_atom(filename:basename(OneFile, filename:extension(OneFile))),
|
|
case getModOpts(Mod) of
|
|
{ok, Options} ->
|
|
setOptions(RootSrcDir, Options);
|
|
_ ->
|
|
ignore
|
|
end;
|
|
_ ->
|
|
ignore
|
|
end,
|
|
setelement(1, Acc, Srcs#{OneFile => MTimeSec})
|
|
end;
|
|
_ ->
|
|
Acc
|
|
end
|
|
end, {#{}, #{}, #{}, #{}}),
|
|
|
|
FunCollAddExtra =
|
|
fun(OneDir, FilesAcc) ->
|
|
filelib:fold_files(?CASE(is_list(OneDir), list_to_binary(OneDir), OneDir), ?RegExp, true,
|
|
fun(OneFile, {Srcs, Hrls, Configs, Beams} = Acc) ->
|
|
MTimeSec = dateTimeToSec(filelib:last_modified(OneFile)),
|
|
case filename:extension(OneFile) of
|
|
<<".beam">> ->
|
|
BeamMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)),
|
|
setelement(4, Acc, Beams#{BeamMod => MTimeSec});
|
|
<<".config">> ->
|
|
setelement(3, Acc, Configs#{OneFile => MTimeSec});
|
|
<<".hrl">> ->
|
|
setelement(2, Acc, Hrls#{OneFile => MTimeSec});
|
|
<<>> ->
|
|
Acc;
|
|
_ ->
|
|
setelement(1, Acc, Srcs#{OneFile => MTimeSec})
|
|
end
|
|
end, FilesAcc)
|
|
end,
|
|
AddExtraCollFiles = lists:foldl(FunCollAddExtra, CollFiles, AddExtraSrcDirs),
|
|
|
|
FunCollAddOnly =
|
|
fun(OneDir, FilesAcc) ->
|
|
filelib:fold_files(?CASE(is_list(OneDir), list_to_binary(OneDir), OneDir), ?RegExp, false,
|
|
fun(OneFile, {Srcs, Hrls, Configs, Beams} = Acc) ->
|
|
MTimeSec = dateTimeToSec(filelib:last_modified(OneFile)),
|
|
case filename:extension(OneFile) of
|
|
<<".beam">> ->
|
|
BeamMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)),
|
|
setelement(4, Acc, Beams#{BeamMod => MTimeSec});
|
|
<<".config">> ->
|
|
setelement(3, Acc, Configs#{OneFile => MTimeSec});
|
|
<<".hrl">> ->
|
|
setelement(2, Acc, Hrls#{OneFile => MTimeSec});
|
|
<<>> ->
|
|
Acc;
|
|
_ ->
|
|
setelement(1, Acc, Srcs#{OneFile => MTimeSec})
|
|
end
|
|
end, FilesAcc)
|
|
end,
|
|
lists:foldl(FunCollAddOnly, AddExtraCollFiles, AddOnlySrcDirs).
|
|
|
|
|
|
isOnlyDir([], _) ->
|
|
true;
|
|
isOnlyDir(ReplaceDirs, SrcDir) ->
|
|
isMatchDir(ReplaceDirs, SrcDir).
|
|
|
|
isDelDir([], _) ->
|
|
false;
|
|
isDelDir(ReplaceDirs, SrcDir) ->
|
|
isMatchDir(ReplaceDirs, SrcDir).
|
|
|
|
isMatchDir([], _SrcDir) ->
|
|
false;
|
|
isMatchDir([SrcDir | _ReplaceDirs], SrcDir) ->
|
|
true;
|
|
isMatchDir([OneDir | ReplaceDirs], SrcDir) ->
|
|
case re:run(SrcDir, OneDir) of
|
|
nomatch -> isMatchDir(ReplaceDirs, SrcDir);
|
|
_ -> true
|
|
end.
|
|
|
|
getEnv(Var, Default) ->
|
|
case application:get_env(eSync, Var) of
|
|
{ok, Value} ->
|
|
Value;
|
|
_ ->
|
|
Default
|
|
end.
|
|
|
|
setEnv(Var, Val) ->
|
|
ok = application:set_env(eSync, Var, Val).
|
|
|
|
canLog(MsgType) ->
|
|
case eSync:getLog() of
|
|
true -> true;
|
|
all -> true;
|
|
none -> false;
|
|
false -> false;
|
|
skip_success -> MsgType == errors orelse MsgType == warnings;
|
|
L when is_list(L) -> lists:member(MsgType, L);
|
|
_ -> false
|
|
end.
|
|
|
|
%% 注意 map类型的数据不能当做key
|
|
-type key() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple().
|
|
-type value() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple() | map().
|
|
|
|
-spec load(term(), [{key(), value()}]) -> ok.
|
|
load(Module, KVs) ->
|
|
Forms = forms(Module, KVs),
|
|
{ok, Module, Bin} = compile:forms(Forms),
|
|
code:soft_purge(Module),
|
|
{module, Module} = code:load_binary(Module, atom_to_list(Module), Bin),
|
|
ok.
|
|
|
|
forms(Module, KVs) ->
|
|
%% -module(Module).
|
|
Mod = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
|
|
%% -export([getv/0]).
|
|
ExportList = [erl_syntax:arity_qualifier(erl_syntax:atom(getv), erl_syntax:integer(1))],
|
|
Export = erl_syntax:attribute(erl_syntax:atom(export), [erl_syntax:list(ExportList)]),
|
|
%% getv(K) -> V
|
|
Function = erl_syntax:function(erl_syntax:atom(getv), lookup_clauses(KVs, [])),
|
|
[erl_syntax:revert(X) || X <- [Mod, Export, Function]].
|
|
|
|
lookup_clause(Key, Value) ->
|
|
Var = erl_syntax:abstract(Key),
|
|
Body = erl_syntax:abstract(Value),
|
|
erl_syntax:clause([Var], [], [Body]).
|
|
|
|
lookup_clause_anon() ->
|
|
Var = erl_syntax:variable("_"),
|
|
Body = erl_syntax:atom(undefined),
|
|
erl_syntax:clause([Var], [], [Body]).
|
|
|
|
lookup_clauses([], Acc) ->
|
|
lists:reverse(lists:flatten([lookup_clause_anon() | Acc]));
|
|
lookup_clauses([{Key, Value} | T], Acc) ->
|
|
lookup_clauses(T, [lookup_clause(Key, Value) | Acc]).
|
|
|
|
getOptions(SrcDir) ->
|
|
case erlang:get(SrcDir) of
|
|
undefined ->
|
|
undefined;
|
|
Options ->
|
|
{ok, Options}
|
|
end.
|
|
|
|
setOptions(SrcDir, Options) ->
|
|
case erlang:get(SrcDir) of
|
|
undefined ->
|
|
erlang:put(SrcDir, Options);
|
|
OldOptions ->
|
|
NewOptions =
|
|
case lists:keytake(compile_info, 1, Options) of
|
|
{value, {compile_info, ValList1}, Options1} ->
|
|
case lists:keytake(compile_info, 1, OldOptions) of
|
|
{value, {compile_info, ValList2}, Options2} ->
|
|
[{compile_info, lists:usort(ValList1 ++ ValList2)} | lists:usort(Options1 ++ Options2)];
|
|
_ ->
|
|
lists:usort(Options ++ OldOptions)
|
|
end;
|
|
_ ->
|
|
lists:usort(Options ++ OldOptions)
|
|
end,
|
|
erlang:put(SrcDir, NewOptions)
|
|
end.
|
|
|
|
loadCfg() ->
|
|
KVs = [{Key, getEnv(Key, DefVal)} || {Key, DefVal} <- ?DefCfgList],
|
|
load(?esCfgSync, KVs).
|
|
|
|
%% ******************************* 加载与编译相关 **********************************************************************
|
|
printResults(_Module, SrcFile, [], []) ->
|
|
?logSuccess("~s Recompiled", [SrcFile]);
|
|
printResults(_Module, SrcFile, [], Warnings) ->
|
|
formatErrors(logWarnings, SrcFile, [], Warnings);
|
|
printResults(_Module, SrcFile, Errors, Warnings) ->
|
|
formatErrors(logErrors, SrcFile, Errors, Warnings).
|
|
|
|
%% @private Print error messages in a pretty and user readable way.
|
|
formatErrors(LogFun, File, Errors, Warnings) ->
|
|
AllErrors1 = lists:sort(lists:flatten([X || {_, X} <- Errors])),
|
|
AllErrors2 = [{Line, "Error", Module, Description} || {Line, Module, Description} <- AllErrors1],
|
|
AllWarnings1 = lists:sort(lists:flatten([X || {_, X} <- Warnings])),
|
|
AllWarnings2 = [{Line, "Warning", Module, Description} || {Line, Module, Description} <- AllWarnings1],
|
|
Everything = lists:sort(AllErrors2 ++ AllWarnings2),
|
|
FPck =
|
|
fun({Line, Prefix, Module, ErrorDescription}) ->
|
|
Msg = formatError(Module, ErrorDescription),
|
|
case LogFun of
|
|
logWarnings ->
|
|
?logWarnings("~s: ~p: ~s: ~s", [File, Line, Prefix, Msg]);
|
|
logErrors ->
|
|
?logErrors("~s: ~p: ~s: ~s", [File, Line, Prefix, Msg])
|
|
end
|
|
end,
|
|
[FPck(X) || X <- Everything],
|
|
ok.
|
|
|
|
formatError(Module, ErrorDescription) ->
|
|
case erlang:function_exported(Module, format_error, 1) of
|
|
true -> Module:format_error(ErrorDescription);
|
|
false -> io_lib:format("~s", [ErrorDescription])
|
|
end.
|
|
|
|
fireOnSync(OnSyncFun, Modules) ->
|
|
case OnSyncFun of
|
|
undefined -> ok;
|
|
Funs when is_list(Funs) -> onSyncApplyList(Funs, Modules);
|
|
Fun -> onSyncApply(Fun, Modules)
|
|
end.
|
|
|
|
onSyncApplyList(Funs, Modules) ->
|
|
[onSyncApply(Fun, Modules) || Fun <- Funs].
|
|
|
|
onSyncApply({M, F}, Modules) ->
|
|
try erlang:apply(M, F, [Modules])
|
|
catch
|
|
C:R:S ->
|
|
?logErrors("apply sync fun ~p:~p(~p) error ~p", [M, F, Modules, {C, R, S}])
|
|
end;
|
|
onSyncApply(Fun, Modules) when is_function(Fun) ->
|
|
try Fun(Modules)
|
|
catch
|
|
C:R:S ->
|
|
?logErrors("apply sync fun ~p(~p) error ~p", [Fun, Modules, {C, R, S}])
|
|
end.
|
|
|
|
reloadChangedMod([], _SwSyncNode, OnSyncFun, Acc) ->
|
|
fireOnSync(OnSyncFun, Acc);
|
|
reloadChangedMod([Module | LeftMod], SwSyncNode, OnSyncFun, Acc) ->
|
|
case Module == es_gen_ipc orelse code:get_object_code(Module) of
|
|
true ->
|
|
ignore;
|
|
error ->
|
|
?logErrors("Error loading object code for ~p", [Module]),
|
|
reloadChangedMod(LeftMod, SwSyncNode, OnSyncFun, Acc);
|
|
{Module, Binary, Filename} ->
|
|
case code:load_binary(Module, Filename, Binary) of
|
|
{module, Module} ->
|
|
?logSuccess("Reloaded(Beam changed) Mod: ~s Success", [Module]),
|
|
syncLoadModOnAllNodes(SwSyncNode, Module, Binary, changed);
|
|
{error, What} ->
|
|
?logErrors("Reloaded(Beam changed) Mod: ~s Errors Reason:~p", [Module, What])
|
|
end,
|
|
reloadChangedMod(LeftMod, SwSyncNode, OnSyncFun, [Module | Acc])
|
|
end.
|
|
|
|
getNodes() ->
|
|
lists:usort(lists:flatten(nodes() ++ [erpc:call(X, erlang, nodes, []) || X <- nodes()])) -- [node()].
|
|
|
|
syncLoadModOnAllNodes(false, _Module, _Binary, _Reason) ->
|
|
ignore;
|
|
syncLoadModOnAllNodes(true, Module, Binary, Reason) ->
|
|
%% Get a list of nodes known by this node, plus all attached nodes.
|
|
Nodes = getNodes(),
|
|
NumNodes = length(Nodes),
|
|
[
|
|
begin
|
|
?logSuccess("Do Reloading '~s' on ~p", [Module, Node]),
|
|
erpc:call(Node, code, ensure_loaded, [Module]),
|
|
case erpc:call(Node, code, which, [Module]) of
|
|
Filename when is_binary(Filename) orelse is_list(Filename) ->
|
|
%% File exists, overwrite and load into VM.
|
|
ok = erpc:call(Node, file, write_file, [Filename, Binary]),
|
|
erpc:call(Node, code, purge, [Module]),
|
|
|
|
case erpc:call(Node, code, load_file, [Module]) of
|
|
{module, Module} ->
|
|
?logSuccess("Reloaded(Beam ~p) Mod:~s and write Success on node:~p", [Reason, Module, Node]);
|
|
{error, What} ->
|
|
?logErrors("Reloaded(Beam ~p) Mod:~s and write Errors on node:~p Reason:~p", [Module, Node, What])
|
|
end;
|
|
_ ->
|
|
%% File doesn't exist, just load into VM.
|
|
case erpc:call(Node, code, load_binary, [Module, undefined, Binary]) of
|
|
{module, Module} ->
|
|
?logSuccess("Reloaded(Beam ~p) Mod:~s Success on node:~p", [Reason, Module, Node]);
|
|
{error, What} ->
|
|
?logErrors("Reloaded(Beam ~p) Mod:~s Errors on node:~p Reason:~p", [Reason, Module, Node, What])
|
|
end
|
|
end
|
|
end || Node <- Nodes
|
|
],
|
|
?logSuccess("Reloaded(Beam changed) Mod: ~s on ~p nodes:~p", [Module, NumNodes, Nodes]).
|
|
|
|
recompileChangeSrcFile(Iterator, SwSyncNode) ->
|
|
case maps:next(Iterator) of
|
|
{File, _V, NextIterator} ->
|
|
recompileSrcFile(File, SwSyncNode),
|
|
recompileChangeSrcFile(NextIterator, SwSyncNode);
|
|
_ ->
|
|
ok
|
|
end.
|
|
|
|
erlydtlCompile(SrcFile, Options) ->
|
|
F =
|
|
fun({outdir, OutDir}, Acc) -> [{out_dir, OutDir} | Acc];
|
|
(OtherOption, Acc) -> [OtherOption | Acc]
|
|
end,
|
|
DtlOptions = lists:foldl(F, [], Options),
|
|
Module = binary_to_atom(lists:flatten(filename:basename(SrcFile, ".dtl") ++ "_dtl")),
|
|
Compiler = erlydtl,
|
|
Compiler:compile(SrcFile, Module, DtlOptions).
|
|
|
|
elixir_compile(SrcFile, Options) ->
|
|
Outdir = proplists:get_value(outdir, Options),
|
|
Compiler = ':Elixir.Kernel.ParallelCompiler',
|
|
Modules = Compiler:files_to_path([SrcFile], Outdir),
|
|
Loader =
|
|
fun(Module) ->
|
|
Outfile = code:which(Module),
|
|
Binary = file:read_file(Outfile),
|
|
{Module, Binary}
|
|
end,
|
|
Results = lists:map(Loader, Modules),
|
|
{ok, multiple, Results, []}.
|
|
|
|
lfe_compile(SrcFile, Options) ->
|
|
Compiler = lfe_comp,
|
|
Compiler:file(SrcFile, Options).
|
|
|
|
getCompileFunAndModuleName(SrcFile) ->
|
|
case getFileType(SrcFile) of
|
|
erl ->
|
|
{fun compile:file/2, binary_to_atom(filename:basename(SrcFile, <<".erl">>))};
|
|
dtl ->
|
|
{fun erlydtlCompile/2, list_to_atom(lists:flatten(binary_to_list(filename:basename(SrcFile, <<".dtl">>)) ++ "_dtl"))};
|
|
lfe ->
|
|
{fun lfe_compile/2, binary_to_atom(filename:basename(SrcFile, <<".lfe">>))};
|
|
elixir ->
|
|
{fun elixir_compile/2, binary_to_atom(filename:basename(SrcFile, <<".ex">>))}
|
|
end.
|
|
|
|
getObjectCode(Module) ->
|
|
case code:get_object_code(Module) of
|
|
{Module, B, Filename} -> {B, Filename};
|
|
_ -> {undefined, undefined}
|
|
end.
|
|
|
|
reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode) ->
|
|
case Binary =/= OldBinary of
|
|
true ->
|
|
%% Try to load the module...
|
|
case code:ensure_loaded(Module) of
|
|
{module, Module} ->
|
|
case code:load_binary(Module, Filename, Binary) of
|
|
{module, Module} ->
|
|
?logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]),
|
|
syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled);
|
|
{error, What} ->
|
|
?logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What])
|
|
end;
|
|
{error, nofile} ->
|
|
case code:load_binary(Module, Filename, Binary) of
|
|
{module, Module} ->
|
|
?logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]),
|
|
syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled);
|
|
{error, What} ->
|
|
?logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What])
|
|
end;
|
|
{error, embedded} ->
|
|
case code:load_file(Module) of %% Module is not yet loaded, load it.
|
|
{module, Module} -> ok;
|
|
{error, nofile} ->
|
|
case code:load_binary(Module, Filename, Binary) of
|
|
{module, Module} ->
|
|
?logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]),
|
|
syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled);
|
|
{error, What} ->
|
|
?logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What])
|
|
end
|
|
end
|
|
end;
|
|
_ ->
|
|
ignore
|
|
end.
|
|
|
|
recompileSrcFile(SrcFile, SwSyncNode) ->
|
|
%% Get the module, src dir, and options...
|
|
RootSrcDir =
|
|
case getRootSrcDirFromSrcDir(SrcFile) of
|
|
undefined ->
|
|
filename:dirname(SrcFile);
|
|
RetSrcDir ->
|
|
RetSrcDir
|
|
end,
|
|
CurSrcDir = filename:dirname(SrcFile),
|
|
{CompileFun, Module} = getCompileFunAndModuleName(SrcFile),
|
|
{OldBinary, Filename} = getObjectCode(Module),
|
|
case Module == es_gen_ipc orelse getOptions(RootSrcDir) of
|
|
true ->
|
|
ignore;
|
|
{ok, Options} ->
|
|
RightFileDir = binary_to_list(filename:join(CurSrcDir, filename:basename(SrcFile))),
|
|
LastOptions = ?CASE(?esCfgSync:getv(?isJustMem), [binary, return | Options], [return | Options]),
|
|
case CompileFun(RightFileDir, LastOptions) of
|
|
{ok, Module, Binary, Warnings} ->
|
|
printResults(Module, RightFileDir, [], Warnings),
|
|
reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode),
|
|
{ok, [], Warnings};
|
|
{ok, [{ok, Module, Binary, Warnings}], Warnings2} ->
|
|
printResults(Module, RightFileDir, [], Warnings ++ Warnings2),
|
|
reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode),
|
|
{ok, [], Warnings ++ Warnings2};
|
|
{ok, multiple, Results, Warnings} ->
|
|
printResults(Module, RightFileDir, [], Warnings),
|
|
[reloadIfNecessary(CompiledModule, OldBinary, Binary, Filename, SwSyncNode) || {CompiledModule, Binary} <- Results],
|
|
{ok, [], Warnings};
|
|
{ok, OtherModule, _Binary, Warnings} ->
|
|
Desc = io_lib:format("Module definition (~p) differs from expected (~s)", [OtherModule, filename:rootname(filename:basename(RightFileDir))]),
|
|
Errors = [{RightFileDir, {0, Module, Desc}}],
|
|
printResults(Module, RightFileDir, Errors, Warnings),
|
|
{ok, Errors, Warnings};
|
|
{error, Errors, Warnings} ->
|
|
printResults(Module, RightFileDir, Errors, Warnings),
|
|
{ok, Errors, Warnings};
|
|
{ok, Module, Warnings} ->
|
|
printResults(Module, RightFileDir, [], Warnings),
|
|
{ok, [], Warnings};
|
|
_Err ->
|
|
?logErrors("compile Mod:~s Errors Reason:~p", [Module, _Err])
|
|
end;
|
|
undefined ->
|
|
case tryGetModOpts(Module) of
|
|
{ok, Options} ->
|
|
setOptions(RootSrcDir, Options),
|
|
recompileSrcFile(SrcFile, SwSyncNode);
|
|
_ ->
|
|
case tryGetSrcOpts(CurSrcDir) of
|
|
{ok, Options} ->
|
|
setOptions(RootSrcDir, Options),
|
|
recompileSrcFile(SrcFile, SwSyncNode);
|
|
_ ->
|
|
?logErrors("Unable to determine options for ~s", [SrcFile])
|
|
end
|
|
end
|
|
end.
|
|
|
|
collIncludeCHrls([], AllHrls, CHrls, NewAddMap) ->
|
|
case maps:size(NewAddMap) > 0 of
|
|
true ->
|
|
collIncludeCHrls(maps:keys(NewAddMap), AllHrls, CHrls, #{});
|
|
_ ->
|
|
CHrls
|
|
end;
|
|
collIncludeCHrls([OneHrl | LeftCHrls], AllHrls, CHrls, NewAddMap) ->
|
|
{NewCHrls, NNewAddMap} = whoInclude(OneHrl, AllHrls, CHrls, NewAddMap),
|
|
collIncludeCHrls(LeftCHrls, AllHrls, NewCHrls, NNewAddMap).
|
|
|
|
collIncludeCErls([], _SrcFiles, CSrcs, _NewAddMap) ->
|
|
CSrcs;
|
|
collIncludeCErls([Hrl | LeftHrl], SrcFiles, CSrcs, NewAddMap) ->
|
|
{NewCSrcs, NNewAddMap} = whoInclude(Hrl, SrcFiles, CSrcs, NewAddMap),
|
|
collIncludeCErls(LeftHrl, SrcFiles, NewCSrcs, NNewAddMap).
|
|
|
|
whoInclude(HrlFile, AllFiles, CFiles, NewAddMap) ->
|
|
HrlFileBaseName = filename:basename(HrlFile),
|
|
doMathEveryFile(maps:iterator(AllFiles), HrlFileBaseName, CFiles, NewAddMap).
|
|
|
|
doMathEveryFile(Iterator, HrlFileBaseName, CFiles, NewAddMap) ->
|
|
case maps:next(Iterator) of
|
|
{OneFile, _V, NextIterator} ->
|
|
case file:open(OneFile, [read, binary]) of
|
|
{ok, IoDevice} ->
|
|
IsInclude = doMathEveryLine(IoDevice, HrlFileBaseName),
|
|
file:close(IoDevice),
|
|
case IsInclude of
|
|
true ->
|
|
case maps:is_key(OneFile, CFiles) of
|
|
true ->
|
|
doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap);
|
|
_ ->
|
|
doMathEveryFile(NextIterator, HrlFileBaseName, CFiles#{OneFile => 1}, NewAddMap#{OneFile => 1})
|
|
end;
|
|
_ ->
|
|
doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap)
|
|
end;
|
|
_ ->
|
|
doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap)
|
|
end;
|
|
_ ->
|
|
{CFiles, NewAddMap}
|
|
end.
|
|
|
|
%% 注释
|
|
%% whoInclude(HrlFile, SrcFiles) ->
|
|
%% HrlFileBaseName = filename:basename(HrlFile),
|
|
%% Pred =
|
|
%% fun(SrcFile, _) ->
|
|
%% {ok, Forms} = epp_dodger:parse_file(SrcFile),
|
|
%% isInclude(binary_to_list(HrlFileBaseName), Forms)
|
|
%% end,
|
|
%% maps:filter(Pred, SrcFiles).
|
|
%% isInclude(_HrlFile, []) ->
|
|
%% false;
|
|
%% isInclude(HrlFile, [{tree, attribute, _, {attribute, _, [{_, _, IncludeFile}]}} | Forms]) when is_list(IncludeFile) ->
|
|
%% IncludeFileBaseName = filename:basename(IncludeFile),
|
|
%% case IncludeFileBaseName of
|
|
%% HrlFile -> true;
|
|
%% _ -> isInclude(HrlFile, Forms)
|
|
%% end;
|
|
%% isInclude(HrlFile, [_SomeForm | Forms]) ->
|
|
%% isInclude(HrlFile, Forms).
|
|
|
|
doMathEveryLine(IoDevice, HrlFileBaseName) ->
|
|
case file:read_line(IoDevice) of
|
|
{ok, Data} ->
|
|
case re:run(Data, HrlFileBaseName) of
|
|
nomatch ->
|
|
case re:run(Data, <<"->">>) of
|
|
nomatch ->
|
|
doMathEveryLine(IoDevice, HrlFileBaseName);
|
|
_ ->
|
|
false
|
|
end;
|
|
{match, [{Start, _Len} | _]} ->
|
|
case binary:at(Data, max(Start - 1, 0)) of
|
|
47 -> %% /
|
|
true;
|
|
34 -> %% "
|
|
true;
|
|
_ ->
|
|
false
|
|
end
|
|
end;
|
|
_ ->
|
|
false
|
|
end.
|
|
|
|
classifyChangeFile([], Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) ->
|
|
{Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams};
|
|
classifyChangeFile([OneFile | LeftFile], Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) ->
|
|
CurMTimeSec = dateTimeToSec(filelib:last_modified(OneFile)),
|
|
case filename:extension(OneFile) of
|
|
<<".beam">> ->
|
|
BinMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)),
|
|
case ColBeams of
|
|
#{BinMod := OldMTimeSec} ->
|
|
case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
|
|
true ->
|
|
classifyChangeFile(LeftFile, [BinMod | Beams], Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams#{BinMod := CurMTimeSec});
|
|
_ ->
|
|
classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
|
|
end;
|
|
_ ->
|
|
classifyChangeFile(LeftFile, [BinMod | Beams], Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams#{BinMod => CurMTimeSec})
|
|
end;
|
|
<<".config">> ->
|
|
AbsFile = filename:absname(OneFile),
|
|
case ColConfigs of
|
|
#{AbsFile := OldMTimeSec} ->
|
|
case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
|
|
true ->
|
|
CfgMod = erlang:binary_to_atom(filename:basename(AbsFile, <<".config">>), utf8),
|
|
classifyChangeFile(LeftFile, Beams, [CfgMod | Configs], Hrls, Srcs, ColSrcs, ColHrls, ColConfigs#{AbsFile := CurMTimeSec}, ColBeams);
|
|
_ ->
|
|
classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
|
|
end;
|
|
_ ->
|
|
CfgMod = erlang:binary_to_atom(filename:basename(AbsFile, <<".config">>), utf8),
|
|
classifyChangeFile(LeftFile, Beams, [CfgMod | Configs], Hrls, Srcs, ColSrcs, ColHrls, ColConfigs#{AbsFile => CurMTimeSec}, ColBeams)
|
|
end;
|
|
<<".hrl">> ->
|
|
AbsFile = filename:absname(OneFile),
|
|
case ColHrls of
|
|
#{AbsFile := OldMTimeSec} ->
|
|
case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
|
|
true ->
|
|
classifyChangeFile(LeftFile, Beams, Configs, Hrls#{AbsFile => 1}, Srcs, ColSrcs, ColHrls#{AbsFile := CurMTimeSec}, ColConfigs, ColBeams);
|
|
_ ->
|
|
classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
|
|
end;
|
|
_ ->
|
|
classifyChangeFile(LeftFile, Beams, Configs, Hrls#{AbsFile => 1}, Srcs, ColSrcs, ColHrls#{AbsFile => CurMTimeSec}, ColConfigs, ColBeams)
|
|
end;
|
|
<<>> ->
|
|
classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams);
|
|
_ ->
|
|
AbsFile = filename:absname(OneFile),
|
|
case ColSrcs of
|
|
#{AbsFile := OldMTimeSec} ->
|
|
case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
|
|
true ->
|
|
classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs#{AbsFile => 1}, ColSrcs#{AbsFile := CurMTimeSec}, ColHrls, ColConfigs, ColBeams);
|
|
_ ->
|
|
classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
|
|
end;
|
|
_ ->
|
|
classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs#{AbsFile => 1}, ColSrcs#{AbsFile => CurMTimeSec}, ColHrls, ColConfigs, ColBeams)
|
|
end
|
|
end.
|
|
|
|
fileSyncPath(ExecName) ->
|
|
case code:priv_dir(?MODULE) of
|
|
{error, _} ->
|
|
case code:which(?MODULE) of
|
|
Filename when is_list(Filename) ->
|
|
filename:join([filename:dirname(filename:dirname(Filename)), "priv", ExecName]);
|
|
_ ->
|
|
filename:join("../priv", ExecName)
|
|
end;
|
|
Dir ->
|
|
filename:join(Dir, ExecName)
|
|
end.
|
|
|
|
dateTimeToSec(DateTime) ->
|
|
if
|
|
DateTime == 0 ->
|
|
0;
|
|
true ->
|
|
erlang:universaltime_to_posixtime(DateTime)
|
|
end.
|
|
|
|
debugInfoKeyFun() ->
|
|
case ?esCfgSync:getv(?debugInfoKeyFun) of
|
|
undefined ->
|
|
{debug_info_key, undefined};
|
|
{Mod, Fun} ->
|
|
Mod:Fun()
|
|
end.
|
|
%% ************************************************* utils end *******************************************************
|