-module(esScanner).
|
|
-include("erlSync.hrl").
|
|
|
|
-behaviour(gen_ipc).
|
|
|
|
-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,
|
|
swSyncNode/1
|
|
]).
|
|
|
|
%% gen_ipc callbacks
|
|
-export([
|
|
init/1,
|
|
handleCall/4,
|
|
handleCast/3,
|
|
handleInfo/3,
|
|
handleOnevent/4,
|
|
terminate/3
|
|
]).
|
|
|
|
-define(SERVER, ?MODULE).
|
|
|
|
-type timestamp() :: file:date_time() | 0.
|
|
-record(state, {
|
|
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_ipc:cast(?SERVER, miCollMods),
|
|
gen_ipc:cast(?SERVER, miCollSrcDirs),
|
|
gen_ipc:cast(?SERVER, miCollSrcFiles),
|
|
gen_ipc:cast(?SERVER, miCompareHrlFiles),
|
|
gen_ipc:cast(?SERVER, miCompareSrcFiles),
|
|
gen_ipc:cast(?SERVER, miCompareBeams),
|
|
esUtils:logSuccess("start Scanning source files..."),
|
|
ok.
|
|
|
|
unpause() ->
|
|
gen_ipc:cast(?SERVER, miUnpause),
|
|
ok.
|
|
|
|
pause() ->
|
|
gen_ipc:cast(?SERVER, miPause),
|
|
esUtils:logSuccess("Pausing erlSync. Call erlSync:run() to restart"),
|
|
ok.
|
|
|
|
curInfo() ->
|
|
gen_ipc: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_ipc:cast(?SERVER, {miSyncNode, IsSync}),
|
|
ok.
|
|
|
|
getOnsync() ->
|
|
gen_ipc:call(?SERVER, miGetOnsync).
|
|
|
|
setOnsync(Fun) ->
|
|
gen_ipc:call(?SERVER, {miSetOnsync, Fun}).
|
|
|
|
%% ************************************ API end ***************************
|
|
|
|
start_link() ->
|
|
gen_ipc:start_link({local, ?SERVER}, ?MODULE, [], []).
|
|
|
|
%% status :: running | pause
|
|
init([]) ->
|
|
erlang:process_flag(trap_exit, true),
|
|
loadCfg(),
|
|
case persistent_term:get(?esRecompileCnt, undefined) of
|
|
undefined ->
|
|
IndexRef = atomics:new(1, [{signed, false}]),
|
|
persistent_term:put(?esRecompileCnt, IndexRef);
|
|
_ ->
|
|
ignore
|
|
end,
|
|
{ok, running, #state{}}.
|
|
|
|
handleCall(miGetOnsync, _, #state{onsyncFun = OnSync} = State, _From) ->
|
|
{reply, OnSync, State};
|
|
handleCall({miSetOnsync, Fun}, _, State, _From) ->
|
|
{reply, ok, State#state{onsyncFun = Fun}};
|
|
handleCall(miCurInfo, _, State, _Form) ->
|
|
{reply, {erlang:get(), State}, State};
|
|
handleCall(_Request, _, _State, _From) ->
|
|
keepStatusState.
|
|
|
|
handleCast(miPause, _, State) ->
|
|
{nextStatus, pause, State};
|
|
handleCast(miUnpause, _, State) ->
|
|
{nextStatus, running, State};
|
|
handleCast(miCollMods, running, State) ->
|
|
AllModules = (erlang:loaded() -- esUtils:getSystemModules()),
|
|
LastCollMods = filterCollMods(AllModules),
|
|
Time = ?esCfgSync:getv(?moduleTime),
|
|
{keepStatus, State#state{modules = LastCollMods}, [?gTimeout(miCollMods, Time)]};
|
|
handleCast(miCollSrcDirs, running, #state{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),
|
|
{keepStatus, State#state{srcDirs = USortedSrcDirs, hrlDirs = USortedHrlDirs}, [?gTimeout(miCollSrcDirs, Time)]};
|
|
handleCast(miCollSrcFiles, running, #state{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),
|
|
{keepStatus, State#state{srcFiles = SrcFiles, hrlFiles = HrlFiles}, [?gTimeout(miCollSrcFiles, Time)]};
|
|
handleCast(miCompareBeams, running, #state{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),
|
|
{keepStatus, State#state{beamTimes = NewBeamLastMod}, [?gTimeout(miCompareBeams, Time)]};
|
|
handleCast(miCompareSrcFiles, running, #state{srcFiles = SrcFiles, srcFileTimes = SrcFileTimes, swSyncNode = SwSyncNode} = State) ->
|
|
atomics:put(persistent_term:get(?esRecompileCnt), 1, 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 atomics:get(persistent_term:get(?esRecompileCnt), 1) > 0 of
|
|
true ->
|
|
gen_ipc:cast(?SERVER, miCompareBeams);
|
|
_ ->
|
|
ignore
|
|
end,
|
|
Time = ?esCfgSync:getv(?compareSrcFileTime),
|
|
{keepStatus, State#state{srcFileTimes = SrcFileTimeList}, [?gTimeout(miCompareSrcFiles, Time)]};
|
|
handleCast(miCompareHrlFiles, running, #state{hrlFiles = HrlFiles, srcFiles = SrcFiles, hrlFileTimes = HrlFileTimes, swSyncNode = SwSyncNode} = State) ->
|
|
atomics:put(persistent_term:get(?esRecompileCnt), 1, 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 atomics:get(persistent_term:get(?esRecompileCnt), 1) > 0 of
|
|
true ->
|
|
gen_ipc:cast(?SERVER, miCompareBeams);
|
|
_ ->
|
|
ignore
|
|
end,
|
|
Time = ?esCfgSync:getv(?compareSrcFileTime),
|
|
{keepStatus, State#state{hrlFileTimes = HrlFileTimeList}, [?gTimeout(miCompareHrlFiles, Time)]};
|
|
handleCast({miSyncNode, IsSync}, _, State) ->
|
|
case IsSync of
|
|
true ->
|
|
{keepStatus, State#state{swSyncNode = true}};
|
|
_ ->
|
|
{keepStatus, State#state{swSyncNode = false}}
|
|
end;
|
|
handleCast(_Msg, _, _State) ->
|
|
keepStatusState.
|
|
|
|
handleInfo(_Msg, _, _State) ->
|
|
keepStatusState.
|
|
|
|
handleOnevent({gTimeout, _}, Msg, Status, State) ->
|
|
handleCast(Msg, Status, State);
|
|
handleOnevent(_EventType, _EventContent, _Status, _State) ->
|
|
keepStatusState.
|
|
|
|
terminate(_Reason, _Status, _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,
|
|
atomics:add(persistent_term:get(?esRecompileCnt), 1, 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 *********************************************
|
|
|