|
|
@ -0,0 +1,691 @@ |
|
|
|
-module(esScanner). |
|
|
|
-include("erlSync.hrl"). |
|
|
|
|
|
|
|
-behaviour(gen_server). |
|
|
|
|
|
|
|
-compile(inline). |
|
|
|
-compile({inline_size, 128}). |
|
|
|
|
|
|
|
%% API |
|
|
|
-export([ |
|
|
|
start_link/0, |
|
|
|
rescan/0, |
|
|
|
pause/0, |
|
|
|
unpause/0, |
|
|
|
setLog/1, |
|
|
|
getLog/0, |
|
|
|
curInfo/0, |
|
|
|
getOnsync/0, |
|
|
|
setOnsync/1, |
|
|
|
isOnlyDir/2, |
|
|
|
swSyncNode/1 |
|
|
|
]). |
|
|
|
|
|
|
|
%% gen_server callbacks |
|
|
|
-export([ |
|
|
|
init/1, |
|
|
|
handle_call/3, |
|
|
|
handle_cast/2, |
|
|
|
handle_info/2, |
|
|
|
terminate/2 |
|
|
|
]). |
|
|
|
|
|
|
|
-define(SERVER, ?MODULE). |
|
|
|
|
|
|
|
-type timestamp() :: file:date_time() | 0. |
|
|
|
-record(state, { |
|
|
|
status = running :: running | pause |
|
|
|
, modules = [] :: [module()] |
|
|
|
, hrlDirs = [] :: [file:filename()] |
|
|
|
, srcDirs = [] :: [file:filename()] |
|
|
|
, hrlFiles = [] :: [file:filename()] |
|
|
|
, srcFiles = [] :: [file:filename()] |
|
|
|
, beamTimes = undefined :: [{module(), timestamp()}] | undefined |
|
|
|
, hrlFileTimes = undefined :: [{file:filename(), timestamp()}] | undefined |
|
|
|
, srcFileTimes = undefined :: [{file:filename(), timestamp()}] | undefined |
|
|
|
, onsyncFun = undefined |
|
|
|
, swSyncNode = false |
|
|
|
}). |
|
|
|
|
|
|
|
%% ************************************ API start *************************** |
|
|
|
rescan() -> |
|
|
|
gen_server:cast(?SERVER, miCollMods), |
|
|
|
gen_server:cast(?SERVER, miCollSrcDirs), |
|
|
|
gen_server:cast(?SERVER, miCollSrcFiles), |
|
|
|
gen_server:cast(?SERVER, miCompareHrlFiles), |
|
|
|
gen_server:cast(?SERVER, miCompareSrcFiles), |
|
|
|
gen_server:cast(?SERVER, miCompareBeams), |
|
|
|
esUtils:logSuccess("start Scanning source files..."), |
|
|
|
ok. |
|
|
|
|
|
|
|
unpause() -> |
|
|
|
gen_server:cast(?SERVER, miUnpause), |
|
|
|
ok. |
|
|
|
|
|
|
|
pause() -> |
|
|
|
gen_server:cast(?SERVER, miPause), |
|
|
|
esUtils:logSuccess("Pausing erlSync. Call erlSync:run() to restart"), |
|
|
|
ok. |
|
|
|
|
|
|
|
curInfo() -> |
|
|
|
gen_server:call(?SERVER, miCurInfo). |
|
|
|
|
|
|
|
setLog(T) when ?LOG_ON(T) -> |
|
|
|
esUtils:setEnv(log, T), |
|
|
|
loadCfg(), |
|
|
|
esUtils:logSuccess("Console Notifications Enabled"), |
|
|
|
ok; |
|
|
|
setLog(_) -> |
|
|
|
esUtils:setEnv(log, none), |
|
|
|
loadCfg(), |
|
|
|
esUtils:logSuccess("Console Notifications Disabled"), |
|
|
|
ok. |
|
|
|
|
|
|
|
getLog() -> |
|
|
|
?esCfgSync:getv(log). |
|
|
|
|
|
|
|
swSyncNode(IsSync) -> |
|
|
|
gen_server:cast(?SERVER, {miSyncNode, IsSync}), |
|
|
|
ok. |
|
|
|
|
|
|
|
getOnsync() -> |
|
|
|
gen_server:call(?SERVER, miGetOnsync). |
|
|
|
|
|
|
|
setOnsync(Fun) -> |
|
|
|
gen_server:call(?SERVER, {miSetOnsync, Fun}). |
|
|
|
|
|
|
|
%% ************************************ API end *************************** |
|
|
|
|
|
|
|
start_link() -> |
|
|
|
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). |
|
|
|
|
|
|
|
init([]) -> |
|
|
|
erlang:process_flag(trap_exit, true), |
|
|
|
loadCfg(), |
|
|
|
erlang:put(?esRecompileCnt, 0), |
|
|
|
{ok, #state{}}. |
|
|
|
|
|
|
|
handle_call(miGetOnsync, _, #state{onsyncFun = OnSync} = State) -> |
|
|
|
{reply, OnSync, State}; |
|
|
|
handle_call({miSetOnsync, Fun}, _, State) -> |
|
|
|
{reply, ok, State#state{onsyncFun = Fun}}; |
|
|
|
handle_call(miCurInfo, _, State) -> |
|
|
|
{reply, {erlang:get(), State}, State}; |
|
|
|
handle_call(_Request, _, State) -> |
|
|
|
{reply, ok, State}. |
|
|
|
|
|
|
|
handle_cast(miPause, State) -> |
|
|
|
{noreply, State#state{status = pause}}; |
|
|
|
handle_cast(miUnpause, State) -> |
|
|
|
{noreply, State#state{status = running}}; |
|
|
|
handle_cast(miCollMods, State) -> |
|
|
|
AllModules = (erlang:loaded() -- esUtils:getSystemModules()), |
|
|
|
LastCollMods = filterCollMods(AllModules), |
|
|
|
Time = ?esCfgSync:getv(?moduleTime), |
|
|
|
?gTimeout(miCollMods, Time), |
|
|
|
{noreply, State#state{modules = LastCollMods}}; |
|
|
|
handle_cast(miCollSrcDirs, #state{status = running, modules = Modules} = State) -> |
|
|
|
{USortedSrcDirs, USortedHrlDirs} = |
|
|
|
case ?esCfgSync:getv(srcDirs) of |
|
|
|
undefined -> |
|
|
|
collSrcDirs(Modules, [], []); |
|
|
|
{add, DirsAndOpts} -> |
|
|
|
collSrcDirs(Modules, addSrcDirs(DirsAndOpts), []); |
|
|
|
{only, DirsAndOpts} -> |
|
|
|
collSrcDirs(Modules, [], addSrcDirs(DirsAndOpts)) |
|
|
|
end, |
|
|
|
Time = ?esCfgSync:getv(?srcDirTime), |
|
|
|
?gTimeout(miCollSrcDirs, Time), |
|
|
|
{noreply, State#state{srcDirs = USortedSrcDirs, hrlDirs = USortedHrlDirs}}; |
|
|
|
handle_cast(miCollSrcFiles, #state{status = running, hrlDirs = HrlDirs, srcDirs = SrcDirs} = State) -> |
|
|
|
FSrc = |
|
|
|
fun(Dir, Acc) -> |
|
|
|
esUtils:wildcard(Dir, ".*\\.(erl|dtl|lfe|ex)$") ++ Acc |
|
|
|
end, |
|
|
|
SrcFiles = lists:usort(lists:foldl(FSrc, [], SrcDirs)), |
|
|
|
FHrl = |
|
|
|
fun(Dir, Acc) -> |
|
|
|
esUtils:wildcard(Dir, ".*\\.hrl$") ++ Acc |
|
|
|
end, |
|
|
|
HrlFiles = lists:usort(lists:foldl(FHrl, [], HrlDirs)), |
|
|
|
Time = ?esCfgSync:getv(?srcFileTime), |
|
|
|
?gTimeout(miCollSrcFiles, Time), |
|
|
|
{noreply, State#state{srcFiles = SrcFiles, hrlFiles = HrlFiles}}; |
|
|
|
handle_cast(miCompareBeams, #state{status = running, modules = Modules, beamTimes = BeamTimes, onsyncFun = OnsyncFun, swSyncNode = SwSyncNode} = State) -> |
|
|
|
BeamTimeList = [{Mod, LastMod} || Mod <- Modules, LastMod <- [modLastmod(Mod)], LastMod =/= 0], |
|
|
|
NewBeamLastMod = lists:usort(BeamTimeList), |
|
|
|
reloadChangedMod(BeamTimes, NewBeamLastMod, SwSyncNode, OnsyncFun, []), |
|
|
|
Time = ?esCfgSync:getv(?compareBeamTime), |
|
|
|
?gTimeout(miCompareBeams, Time), |
|
|
|
{noreply, State#state{beamTimes = NewBeamLastMod}}; |
|
|
|
handle_cast(miCompareSrcFiles, #state{status = running, srcFiles = SrcFiles, srcFileTimes = SrcFileTimes, swSyncNode = SwSyncNode} = State) -> |
|
|
|
erlang:put(?esRecompileCnt, 0), |
|
|
|
%% Create a list of file lastmod times... |
|
|
|
SrcFileTimeList = [{Src, LastMod} || Src <- SrcFiles, LastMod <- [filelib:last_modified(Src)], LastMod =/= 0], |
|
|
|
%% NewSrcFileLastMod = lists:usort(SrcFileTimeList), |
|
|
|
%% Compare to previous results, if there are changes, then recompile the file... |
|
|
|
recompileChangeSrcFile(SrcFileTimes, SrcFileTimeList, SwSyncNode), |
|
|
|
case erlang:get(?esRecompileCnt) > 0 of |
|
|
|
true -> |
|
|
|
gen_server:cast(?SERVER, miCompareBeams); |
|
|
|
_ -> |
|
|
|
ignore |
|
|
|
end, |
|
|
|
Time = ?esCfgSync:getv(?compareSrcFileTime), |
|
|
|
?gTimeout(miCompareSrcFiles, Time), |
|
|
|
{noreply, State#state{srcFileTimes = SrcFileTimeList}}; |
|
|
|
handle_cast(miCompareHrlFiles, #state{status = running, hrlFiles = HrlFiles, srcFiles = SrcFiles, hrlFileTimes = HrlFileTimes, swSyncNode = SwSyncNode} = State) -> |
|
|
|
erlang:put(?esRecompileCnt, 0), |
|
|
|
%% Create a list of file lastmod times... |
|
|
|
HrlFileTimeList = [{Hrl, LastMod} || Hrl <- HrlFiles, LastMod <- [filelib:last_modified(Hrl)], LastMod =/= 0], |
|
|
|
%% NewHrlFileLastMod = lists:usort(HrlFileTimeList), |
|
|
|
%% Compare to previous results, if there are changes, then recompile src files that depends |
|
|
|
recompileChangeHrlFile(HrlFileTimes, HrlFileTimeList, SrcFiles, SwSyncNode), |
|
|
|
case erlang:get(?esRecompileCnt) > 0 of |
|
|
|
true -> |
|
|
|
gen_server:cast(?SERVER, miCompareBeams); |
|
|
|
_ -> |
|
|
|
ignore |
|
|
|
end, |
|
|
|
Time = ?esCfgSync:getv(?compareSrcFileTime), |
|
|
|
?gTimeout(miCompareHrlFiles, Time), |
|
|
|
{noreply, State#state{hrlFileTimes = HrlFileTimeList}}; |
|
|
|
handle_cast({miSyncNode, IsSync}, State) -> |
|
|
|
case IsSync of |
|
|
|
true -> |
|
|
|
{noreply, State#state{swSyncNode = true}}; |
|
|
|
_ -> |
|
|
|
{noreply, State#state{swSyncNode = false}} |
|
|
|
end; |
|
|
|
handle_cast(_Msg, State) -> |
|
|
|
esUtils:logSuccess("recv unexpect cast msg..."), |
|
|
|
{noreply, State}. |
|
|
|
|
|
|
|
|
|
|
|
handle_info({timeout, _Ref, {doSync, Msg}}, State) -> |
|
|
|
erlang:erase({pdTimerRef, Msg}), |
|
|
|
handle_cast(Msg, State); |
|
|
|
handle_info(_Msg, State) -> |
|
|
|
{noreply, State}. |
|
|
|
|
|
|
|
|
|
|
|
terminate(_Reason, _State) -> |
|
|
|
ok. |
|
|
|
|
|
|
|
%% ***********************************PRIVATE FUNCTIONS start ******************************************* |
|
|
|
|
|
|
|
addSrcDirs(DirsAndOpts) -> |
|
|
|
[ |
|
|
|
begin |
|
|
|
%% ensure module out path exists & in our code path list |
|
|
|
case proplists:get_value(outdir, Opts) of |
|
|
|
undefined -> |
|
|
|
true; |
|
|
|
Path -> |
|
|
|
ok = filelib:ensure_dir(Path), |
|
|
|
true = code:add_pathz(Path) |
|
|
|
end, |
|
|
|
setOptions(Dir, Opts), |
|
|
|
Dir |
|
|
|
end || {Dir, Opts} <- DirsAndOpts |
|
|
|
]. |
|
|
|
|
|
|
|
reloadChangedMod([{Module, LastMod} | T1], [{Module, LastMod} | T2], SwSyncNode, OnsyncFun, Acc) -> |
|
|
|
reloadChangedMod(T1, T2, SwSyncNode, OnsyncFun, Acc); |
|
|
|
reloadChangedMod([{Module, _} | T1], [{Module, _} | T2], SwSyncNode, OnsyncFun, Acc) -> |
|
|
|
case code:get_object_code(Module) of |
|
|
|
error -> |
|
|
|
Msg = io_lib:format("Error loading object code for ~p", [Module]), |
|
|
|
esUtils:logErrors(Msg), |
|
|
|
reloadChangedMod(T1, T2, SwSyncNode, OnsyncFun, Acc); |
|
|
|
{Module, Binary, Filename} -> |
|
|
|
case code:load_binary(Module, Filename, Binary) of |
|
|
|
{module, Module} -> |
|
|
|
Msg = io_lib:format("Reloaded(Beam changed) Mod:~s Success", [Module]), |
|
|
|
esUtils:logSuccess(Msg); |
|
|
|
{error, What} -> |
|
|
|
Msg = io_lib:format("Reloaded(Beam changed) Mod:~s Errors Reason:~p", [Module, What]), |
|
|
|
esUtils:logErrors(Msg) |
|
|
|
end, |
|
|
|
case SwSyncNode of |
|
|
|
true -> |
|
|
|
{ok, NumNodes, Nodes} = syncLoadModOnAllNodes(Module), |
|
|
|
MsgNodes = io_lib:format("Reloaded(Beam changed) Mod:~s on ~p nodes:~p", [Module, NumNodes, Nodes]), |
|
|
|
esUtils:logSuccess(MsgNodes); |
|
|
|
false -> |
|
|
|
ignore |
|
|
|
end, |
|
|
|
reloadChangedMod(T1, T2, SwSyncNode, OnsyncFun, [Module | Acc]) |
|
|
|
end; |
|
|
|
reloadChangedMod([{Module1, _LastMod1} | T1] = OldLastMods, [{Module2, _LastMod2} | T2] = NewLastMods, SwSyncNode, OnsyncFun, Acc) -> |
|
|
|
%% Lists are different, advance the smaller one... |
|
|
|
case Module1 < Module2 of |
|
|
|
true -> |
|
|
|
reloadChangedMod(T1, NewLastMods, SwSyncNode, OnsyncFun, Acc); |
|
|
|
false -> |
|
|
|
reloadChangedMod(OldLastMods, T2, SwSyncNode, OnsyncFun, Acc) |
|
|
|
end; |
|
|
|
reloadChangedMod(A, B, _SwSyncNode, OnsyncFun, Acc) when A =:= []; B =:= [] -> |
|
|
|
fireOnsync(OnsyncFun, Acc), |
|
|
|
ok; |
|
|
|
reloadChangedMod(undefined, _Other, _, _, _) -> |
|
|
|
ok. |
|
|
|
|
|
|
|
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) -> |
|
|
|
erlang:apply(M, F, [Modules]); |
|
|
|
onsyncApply(Fun, Modules) when is_function(Fun) -> |
|
|
|
Fun(Modules). |
|
|
|
|
|
|
|
getNodes() -> |
|
|
|
lists:usort(lists:flatten(nodes() ++ [rpc:call(X, erlang, nodes, []) || X <- nodes()])) -- [node()]. |
|
|
|
|
|
|
|
syncLoadModOnAllNodes(Module) -> |
|
|
|
%% Get a list of nodes known by this node, plus all attached nodes. |
|
|
|
Nodes = getNodes(), |
|
|
|
NumNodes = length(Nodes), |
|
|
|
{Module, Binary, _} = code:get_object_code(Module), |
|
|
|
FSync = |
|
|
|
fun(Node) -> |
|
|
|
MsgNode = io_lib:format("Reloading '~s' on ~p", [Module, Node]), |
|
|
|
esUtils:logSuccess(MsgNode), |
|
|
|
rpc:call(Node, code, ensure_loaded, [Module]), |
|
|
|
case rpc:call(Node, code, which, [Module]) of |
|
|
|
Filename when is_binary(Filename) orelse is_list(Filename) -> |
|
|
|
%% File exists, overwrite and load into VM. |
|
|
|
ok = rpc:call(Node, file, write_file, [Filename, Binary]), |
|
|
|
rpc:call(Node, code, purge, [Module]), |
|
|
|
|
|
|
|
case rpc:call(Node, code, load_file, [Module]) of |
|
|
|
{module, Module} -> |
|
|
|
Msg = io_lib:format("Reloaded(Beam changed) Mod:~s and write Success on node:~p", [Module, Node]), |
|
|
|
esUtils:logSuccess(Msg); |
|
|
|
{error, What} -> |
|
|
|
Msg = io_lib:format("Reloaded(Beam changed) Mod:~s and write Errors on node:~p Reason:~p", [Module, Node, What]), |
|
|
|
esUtils:logErrors(Msg) |
|
|
|
end; |
|
|
|
_ -> |
|
|
|
%% File doesn't exist, just load into VM. |
|
|
|
case rpc:call(Node, code, load_binary, [Module, undefined, Binary]) of |
|
|
|
{module, Module} -> |
|
|
|
Msg = io_lib:format("Reloaded(Beam changed) Mod:~s Success on node:~p", [Module, Node]), |
|
|
|
esUtils:logSuccess(Msg); |
|
|
|
{error, What} -> |
|
|
|
Msg = io_lib:format("Reloaded(Beam changed) Mod:~s Errors on node:~p Reason:~p", [Module, Node, What]), |
|
|
|
esUtils:logErrors(Msg) |
|
|
|
end |
|
|
|
end |
|
|
|
end, |
|
|
|
[FSync(X) || X <- Nodes], |
|
|
|
{ok, NumNodes, Nodes}. |
|
|
|
|
|
|
|
recompileChangeSrcFile([{File, LastMod} | T1], [{File, LastMod} | T2], SwSyncNode) -> |
|
|
|
recompileChangeSrcFile(T1, T2, SwSyncNode); |
|
|
|
recompileChangeSrcFile([{File, _} | T1], [{File, _} | T2], SwSyncNode) -> |
|
|
|
recompileSrcFile(File, SwSyncNode), |
|
|
|
recompileChangeSrcFile(T1, T2, SwSyncNode); |
|
|
|
recompileChangeSrcFile([{File1, _LastMod1} | T1] = OldSrcFiles, [{File2, LastMod2} | T2] = NewSrcFiles, SwSyncNode) -> |
|
|
|
%% Lists are different... |
|
|
|
case File1 < File2 of |
|
|
|
true -> |
|
|
|
%% File was removed, do nothing... |
|
|
|
recompileChangeSrcFile(T1, NewSrcFiles, SwSyncNode); |
|
|
|
false -> |
|
|
|
maybeRecompileSrcFile(File2, LastMod2, SwSyncNode), |
|
|
|
recompileChangeSrcFile(OldSrcFiles, T2, SwSyncNode) |
|
|
|
end; |
|
|
|
recompileChangeSrcFile([], [{File, LastMod} | T2], SwSyncNode) -> |
|
|
|
maybeRecompileSrcFile(File, LastMod, SwSyncNode), |
|
|
|
recompileChangeSrcFile([], T2, SwSyncNode); |
|
|
|
recompileChangeSrcFile(_A, [], _) -> |
|
|
|
%% All remaining files, if any, were removed. |
|
|
|
ok; |
|
|
|
recompileChangeSrcFile(undefined, _Other, _) -> |
|
|
|
ok. |
|
|
|
|
|
|
|
erlydtlCompile(SrcFile, Options) -> |
|
|
|
F = |
|
|
|
fun({outdir, OutDir}, Acc) -> [{out_dir, OutDir} | Acc]; |
|
|
|
(OtherOption, Acc) -> [OtherOption | Acc] |
|
|
|
end, |
|
|
|
DtlOptions = lists:foldl(F, [], Options), |
|
|
|
Module = list_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([list_to_binary(SrcFile)], list_to_binary(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). |
|
|
|
|
|
|
|
maybeRecompileSrcFile(File, LastMod, SwSyncNode) -> |
|
|
|
Module = list_to_atom(filename:basename(File, ".erl")), |
|
|
|
case code:which(Module) of |
|
|
|
BeamFile when is_list(BeamFile) -> |
|
|
|
%% check with beam file |
|
|
|
case filelib:last_modified(BeamFile) of |
|
|
|
BeamLastMod when LastMod > BeamLastMod -> |
|
|
|
recompileSrcFile(File, SwSyncNode); |
|
|
|
_ -> |
|
|
|
ok |
|
|
|
end; |
|
|
|
_ -> |
|
|
|
%% File is new, recompile... |
|
|
|
recompileSrcFile(File, SwSyncNode) |
|
|
|
end. |
|
|
|
|
|
|
|
getCompileFunAndModuleName(SrcFile) -> |
|
|
|
case esUtils:getFileType(SrcFile) of |
|
|
|
erl -> |
|
|
|
{fun compile:file/2, list_to_atom(filename:basename(SrcFile, ".erl"))}; |
|
|
|
dtl -> |
|
|
|
{fun erlydtlCompile/2, list_to_atom(lists:flatten(filename:basename(SrcFile, ".dtl") ++ "_dtl"))}; |
|
|
|
lfe -> |
|
|
|
{fun lfe_compile/2, list_to_atom(filename:basename(SrcFile, ".lfe"))}; |
|
|
|
elixir -> |
|
|
|
{fun elixir_compile/2, list_to_atom(filename:basename(SrcFile, ".ex"))} |
|
|
|
end. |
|
|
|
|
|
|
|
getObjectCode(Module) -> |
|
|
|
case code:get_object_code(Module) of |
|
|
|
{Module, B, _Filename} -> B; |
|
|
|
_ -> undefined |
|
|
|
end. |
|
|
|
|
|
|
|
reloadIfNecessary(_CompileFun, _SrcFile, _Module, Binary, Binary, _Options) -> |
|
|
|
ok; |
|
|
|
reloadIfNecessary(CompileFun, SrcFile, Module, _OldBinary, _Binary, Options) -> |
|
|
|
%% Compiling changed the beam code. Compile and reload. |
|
|
|
CompileFun(SrcFile, Options), |
|
|
|
%% Try to load the module... |
|
|
|
case code:ensure_loaded(Module) of |
|
|
|
{module, Module} -> ok; |
|
|
|
{error, nofile} -> errorNoFile(Module); |
|
|
|
{error, embedded} -> |
|
|
|
case code:load_file(Module) of %% Module is not yet loaded, load it. |
|
|
|
{module, Module} -> ok; |
|
|
|
{error, nofile} -> errorNoFile(Module) |
|
|
|
end |
|
|
|
end, |
|
|
|
erlang:put(?esRecompileCnt, erlang:get(?esRecompileCnt) + 1). |
|
|
|
|
|
|
|
errorNoFile(Module) -> |
|
|
|
Msg = io_lib:format("~p Couldn't load module: nofile", [Module]), |
|
|
|
esUtils:logWarnings([Msg]). |
|
|
|
|
|
|
|
recompileSrcFile(SrcFile, SwSyncNode) -> |
|
|
|
%% Get the module, src dir, and options... |
|
|
|
case esUtils:getSrcDir(SrcFile) of |
|
|
|
{ok, SrcDir} -> |
|
|
|
{CompileFun, Module} = getCompileFunAndModuleName(SrcFile), |
|
|
|
OldBinary = getObjectCode(Module), |
|
|
|
case getOptions(SrcDir) of |
|
|
|
{ok, Options} -> |
|
|
|
case CompileFun(SrcFile, [binary, return | Options]) of |
|
|
|
{ok, Module, Binary, Warnings} -> |
|
|
|
reloadIfNecessary(CompileFun, SrcFile, Module, OldBinary, Binary, Options), |
|
|
|
printResults(Module, SrcFile, [], Warnings), |
|
|
|
{ok, [], Warnings}; |
|
|
|
{ok, [{ok, Module, Binary, Warnings}], Warnings2} -> |
|
|
|
reloadIfNecessary(CompileFun, SrcFile, Module, OldBinary, Binary, Options), |
|
|
|
printResults(Module, SrcFile, [], Warnings ++ Warnings2), |
|
|
|
{ok, [], Warnings ++ Warnings2}; |
|
|
|
{ok, multiple, Results, Warnings} -> |
|
|
|
[reloadIfNecessary(CompileFun, SrcFile, CompiledModule, OldBinary, Binary, Options) || {CompiledModule, Binary} <- Results], |
|
|
|
printResults(Module, SrcFile, [], Warnings), |
|
|
|
{ok, [], Warnings}; |
|
|
|
{ok, OtherModule, _Binary, Warnings} -> |
|
|
|
Desc = io_lib:format("Module definition (~p) differs from expected (~s)", [OtherModule, filename:rootname(filename:basename(SrcFile))]), |
|
|
|
Errors = [{SrcFile, {0, Module, Desc}}], |
|
|
|
printResults(Module, SrcFile, Errors, Warnings), |
|
|
|
{ok, Errors, Warnings}; |
|
|
|
{error, Errors, Warnings} -> |
|
|
|
printResults(Module, SrcFile, Errors, Warnings), |
|
|
|
{ok, Errors, Warnings} |
|
|
|
end; |
|
|
|
undefined -> |
|
|
|
case esUtils:tryGetModOptions(Module) of |
|
|
|
{ok, Options} -> |
|
|
|
setOptions(SrcDir, Options), |
|
|
|
recompileSrcFile(SrcFile, SwSyncNode); |
|
|
|
_ -> |
|
|
|
Msg = io_lib:format("Unable to determine options for ~s", [SrcFile]), |
|
|
|
esUtils:logErrors(Msg) |
|
|
|
end |
|
|
|
end; |
|
|
|
_ -> |
|
|
|
Msg = io_lib:format("not find the file ~s", [SrcFile]), |
|
|
|
esUtils:logErrors(Msg) |
|
|
|
end. |
|
|
|
|
|
|
|
printResults(_Module, SrcFile, [], []) -> |
|
|
|
Msg = io_lib:format("~s Recompiled", [SrcFile]), |
|
|
|
esUtils:logSuccess(lists:flatten(Msg)); |
|
|
|
printResults(_Module, SrcFile, [], Warnings) -> |
|
|
|
Msg = [formatErrors(SrcFile, [], Warnings), io_lib:format("~s Recompiled with ~p warnings", [SrcFile, length(Warnings)])], |
|
|
|
esUtils:logWarnings(Msg); |
|
|
|
printResults(_Module, SrcFile, Errors, Warnings) -> |
|
|
|
Msg = [formatErrors(SrcFile, Errors, Warnings)], |
|
|
|
esUtils:logErrors(Msg). |
|
|
|
|
|
|
|
|
|
|
|
%% @private Print error messages in a pretty and user readable way. |
|
|
|
formatErrors(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), |
|
|
|
io_lib:format("~s:~p: ~s: ~s", [File, Line, Prefix, Msg]) |
|
|
|
end, |
|
|
|
[FPck(X) || X <- Everything]. |
|
|
|
|
|
|
|
formatError(Module, ErrorDescription) -> |
|
|
|
case erlang:function_exported(Module, format_error, 1) of |
|
|
|
true -> Module:format_error(ErrorDescription); |
|
|
|
false -> io_lib:format("~s", [ErrorDescription]) |
|
|
|
end. |
|
|
|
|
|
|
|
recompileChangeHrlFile([{File, LastMod} | T1], [{File, LastMod} | T2], SrcFiles, SwSyncNode) -> |
|
|
|
recompileChangeHrlFile(T1, T2, SrcFiles, SwSyncNode); |
|
|
|
recompileChangeHrlFile([{File, _} | T1], [{File, _} | T2], SrcFiles, SwSyncNode) -> |
|
|
|
WhoInclude = whoInclude(File, SrcFiles), |
|
|
|
[recompileSrcFile(SrcFile, SwSyncNode) || SrcFile <- WhoInclude], |
|
|
|
recompileChangeHrlFile(T1, T2, SrcFiles, SwSyncNode); |
|
|
|
recompileChangeHrlFile([{File1, _LastMod1} | T1] = OldHrlFiles, [{File2, LastMod2} | T2] = NewHrlFiles, SrcFiles, SwSyncNode) -> |
|
|
|
%% Lists are different... |
|
|
|
case File1 < File2 of |
|
|
|
true -> |
|
|
|
%% File was removed, do nothing... |
|
|
|
warnDelHrlFiles(File1, SrcFiles), |
|
|
|
recompileChangeHrlFile(T1, NewHrlFiles, SrcFiles, SwSyncNode); |
|
|
|
false -> |
|
|
|
%% File is new, look for src that include it |
|
|
|
WhoInclude = whoInclude(File2, SrcFiles), |
|
|
|
[maybeRecompileSrcFile(SrcFile, LastMod2, SwSyncNode) || SrcFile <- WhoInclude], |
|
|
|
recompileChangeHrlFile(OldHrlFiles, T2, SrcFiles, SwSyncNode) |
|
|
|
end; |
|
|
|
recompileChangeHrlFile([], [{File, LastMod} | T2], SrcFiles, SwSyncNode) -> |
|
|
|
WhoInclude = whoInclude(File, SrcFiles), |
|
|
|
[maybeRecompileSrcFile(SrcFile, LastMod, SwSyncNode) || SrcFile <- WhoInclude], |
|
|
|
recompileChangeHrlFile([], T2, SrcFiles, SwSyncNode); |
|
|
|
recompileChangeHrlFile([{File1, _LastMod1} | T1], [], SrcFiles, SwSyncNode) -> |
|
|
|
warnDelHrlFiles(File1, SrcFiles), |
|
|
|
recompileChangeHrlFile(T1, [], SrcFiles, SwSyncNode); |
|
|
|
recompileChangeHrlFile([], [], _, _) -> |
|
|
|
%% Done |
|
|
|
ok; |
|
|
|
recompileChangeHrlFile(undefined, _Other, _, _) -> |
|
|
|
%% First load, do nothing |
|
|
|
ok. |
|
|
|
|
|
|
|
warnDelHrlFiles(HrlFile, SrcFiles) -> |
|
|
|
WhoInclude = whoInclude(HrlFile, SrcFiles), |
|
|
|
case WhoInclude of |
|
|
|
[] -> ok; |
|
|
|
_ -> |
|
|
|
Msg = io_lib:format("Warning. Deleted ~p file included in existing src files: ~p", [filename:basename(HrlFile), lists:map(fun(File) -> |
|
|
|
filename:basename(File) end, WhoInclude)]), |
|
|
|
esUtils:logSuccess(lists:flatten(Msg)) |
|
|
|
end. |
|
|
|
|
|
|
|
whoInclude(HrlFile, SrcFiles) -> |
|
|
|
HrlFileBaseName = filename:basename(HrlFile), |
|
|
|
Pred = |
|
|
|
fun(SrcFile) -> |
|
|
|
{ok, Forms} = epp_dodger:parse_file(SrcFile), |
|
|
|
isInclude(HrlFileBaseName, Forms) |
|
|
|
end, |
|
|
|
lists: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). |
|
|
|
|
|
|
|
filterCollMods(Modules) -> |
|
|
|
excludeMods(onlyMods(Modules)). |
|
|
|
|
|
|
|
onlyMods(Modules) -> |
|
|
|
case ?esCfgSync:getv(?onlyMods) of |
|
|
|
[] -> |
|
|
|
Modules; |
|
|
|
OnlyMods -> |
|
|
|
[Mod || Mod <- Modules, checkModIsMatch(OnlyMods, Mod) == true] |
|
|
|
end. |
|
|
|
|
|
|
|
excludeMods(Modules) -> |
|
|
|
case ?esCfgSync:getv(?excludedMods) of |
|
|
|
[] -> |
|
|
|
Modules; |
|
|
|
ExcludedModules -> |
|
|
|
[Mod || Mod <- Modules, checkModIsMatch(ExcludedModules, Mod) == false] |
|
|
|
end. |
|
|
|
|
|
|
|
checkModIsMatch([], _Module) -> |
|
|
|
false; |
|
|
|
checkModIsMatch([ModOrPattern | T], Module) -> |
|
|
|
case ModOrPattern of |
|
|
|
Module -> |
|
|
|
true; |
|
|
|
_ when is_list(ModOrPattern) -> |
|
|
|
case re:run(atom_to_list(Module), ModOrPattern) of |
|
|
|
{match, _} -> |
|
|
|
true; |
|
|
|
nomatch -> |
|
|
|
checkModIsMatch(T, Module) |
|
|
|
end; |
|
|
|
_ -> |
|
|
|
checkModIsMatch(T, Module) |
|
|
|
end. |
|
|
|
|
|
|
|
collSrcDirs(Modules, AddDirs, OnlyDirs) -> |
|
|
|
FColl = |
|
|
|
fun |
|
|
|
(Mod, {SrcAcc, HrlAcc} = Acc) -> |
|
|
|
case esUtils:getModSrcDir(Mod) of |
|
|
|
{ok, SrcDir} -> |
|
|
|
case isOnlyDir(OnlyDirs, SrcDir) of |
|
|
|
true -> |
|
|
|
{ok, Options} = esUtils:getModOptions(Mod), |
|
|
|
HrlDir = proplists:get_all_values(i, Options), |
|
|
|
setOptions(SrcDir, Options), |
|
|
|
{[SrcDir | SrcAcc], HrlDir ++ HrlAcc}; |
|
|
|
_ -> |
|
|
|
Acc |
|
|
|
end; |
|
|
|
undefined -> |
|
|
|
Acc |
|
|
|
end |
|
|
|
end, |
|
|
|
{SrcDirs, HrlDirs} = lists:foldl(FColl, {AddDirs, []}, Modules), |
|
|
|
USortedSrcDirs = lists:usort(SrcDirs), |
|
|
|
USortedHrlDirs = lists:usort(HrlDirs), |
|
|
|
{USortedSrcDirs, USortedHrlDirs}. |
|
|
|
|
|
|
|
isOnlyDir([], _) -> |
|
|
|
true; |
|
|
|
isOnlyDir(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. |
|
|
|
|
|
|
|
modLastmod(Mod) -> |
|
|
|
case code:which(Mod) of |
|
|
|
Beam when is_list(Beam) -> |
|
|
|
filelib:last_modified(Beam); |
|
|
|
_Other -> |
|
|
|
0 %% non_existing | cover_compiled | preloaded |
|
|
|
end. |
|
|
|
|
|
|
|
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, esUtils:getEnv(Key, DefVal)} || {Key, DefVal} <- ?CfgList], |
|
|
|
esUtils:load(?esCfgSync, KVs). |
|
|
|
%% ***********************************PRIVATE FUNCTIONS end ********************************************* |
|
|
|
|