diff --git a/erlSync.sample.config b/erlSync.sample.config index b8599f8..44f607e 100644 --- a/erlSync.sample.config +++ b/erlSync.sample.config @@ -1,30 +1,26 @@ [ {erlSync, [ - %% 扫描module的间隔时间 - {moduleTime, 40000}, - %% 扫描 源码目录的间隔时间 - {srcDirTime, 6000}, - %% 扫描 源码文件的间隔时间 - {srcFileTime, 6000}, - %% 对比加载beam 文件的间隔时间 - {compareBeamTime, 2000}, - %% 对比编译erl hrl文件的间隔时间 - {compareSrcFileTime, 2000}, - + %% 接受fileSync的监听端口 {listenPort, 12369}, + %% 编译命令 支持项目自带的编译命令 也可以用该项目自带的编译逻辑 + %% "rebar3 compile" | "start compile.bat" | "make" + {compileCmd, undefined}, + %% 编译和加载以及其他一些日志的提示级别设置 %% 有效值: all | none | [success | warnings | errors] %% 默认值: all {log, all}, %% 这个参数用于设置特殊目录下的文件检查编译与加载 - %% 格式:{srcDirs, {strategy(), [srcDirDescr()]}} | {srcDirs, undefined} - %% -type strategy() :: add | only. + %% 格式:{extraDirs, [{strategy(), [srcDirDescr()]}} | {srcDirs, undefined]} + %% -type strategy() :: add | only | del. %% 如果 strategy() is only, 仅仅扫描指定目录下的文件编译与加载. 如果 strategy() is add, 会扫描添加的指定目录同步编译与加载. %% -type srcDirDescr() :: { Dir :: file:filename(), [Options :: compile_option()]}. %% 默认值:undefined 根据当前工作目录 和 已经加载的模块做来得出需要扫描的目录 - {srcDirs, {strategy(), [srcDirDescr()]}}, + %%示例: {extraDirs, [{add, [{"./_build/default/lib/erlGbh", []}]}, {only, [{"./", []}]}, {del, [{"./_build", []}]}]}. + %%{extraDirs, [{add, [{"./_build/default/lib/erlGbh", []}, {"./_build/default/lib/erlSync/ebin", []}]}, {only, [{"./", []}]}, {del, [{"./_build", []}]}]} + {extraDirs, [{strategy(), [srcDirDescr()]}]}, %% 这个参数用来设置 怎么处理 当beam文件的源文件目录不是当前工作的子目录时的情况 %% 有效值: fix | allow | ignore @@ -32,15 +28,7 @@ %% * allow = 不要做任何特别的事情,使用beam源文件原始路径查找该文件 %% * ignore = 而忽略对其源路径的任何更改 %% 默认值: fix - {descendant, fix}, - - %% 仅仅同步编译和加载该参数指定的模块 - %% default: [] 为空的时候 该参数无效 - {onlyMods, []}, - - %% 不同步编译和加载该参数指定的模块 - %% default: [] 为空的时候 该参数无效 - {excludedMods, []} + {descendant, fix} ]} ]. diff --git a/include/erlSync.hrl b/include/erlSync.hrl index 4f2229a..f629449 100644 --- a/include/erlSync.hrl +++ b/include/erlSync.hrl @@ -14,17 +14,11 @@ , {exit_on_close, true}]). -define(Log, log). --define(moduleTime, moduleTime). --define(srcDirTime, srcDirTime). --define(srcFileTime, srcFileTime). --define(compareBeamTime, compareBeamTime). --define(compareSrcFileTime, compareSrcFileTime). -define(listenPort, listenPort). --define(srcDirs, srcDirs). --define(onlyMods, onlyMods). --define(excludedMods, excludedMods). +-define(compileCmd, compileCmd). +-define(extraDirs, extraDirs). -define(descendant, descendant). --define(CfgList, [{?Log, all}, {?moduleTime, 30000}, {?srcDirTime, 6000}, {?srcFileTime, 6000}, {?compareBeamTime, 4000}, {?compareSrcFileTime, 4000}, {?listenPort, 12369}, {?srcDirs, undefined}, {?onlyMods, []}, {?excludedMods, []}, {?descendant, fix}]). +-define(CfgList, [{?Log, all}, {?listenPort, 12369}, {?compileCmd, undefined}, {?extraDirs, undefined}, {?descendant, fix}]). -define(esCfgSync, esCfgSync). diff --git a/priv/fileSync b/priv/fileSync new file mode 100644 index 0000000..74b0072 Binary files /dev/null and b/priv/fileSync differ diff --git a/priv/fileSync.exe b/priv/fileSync.exe index 3241345..7a44338 100644 Binary files a/priv/fileSync.exe and b/priv/fileSync.exe differ diff --git a/src/erlSync.erl b/src/erlSync.erl index bcb3a56..192dad2 100644 --- a/src/erlSync.erl +++ b/src/erlSync.erl @@ -23,39 +23,39 @@ stop() -> run() -> case start() of {ok, _Started} -> - esScanner:unpause(), - esScanner:rescan(), + esSyncSrv:unpause(), + esSyncSrv:rescan(), ok; {error, Reason} -> - Msg = io_lib:format("start erlSync error ~p~n", [Reason]), + Msg = io_lib:format("start erlSync error:~p~n", [Reason]), esUtils:logErrors(Msg) end. pause() -> - esScanner:pause(). + esSyncSrv:pause(). swSyncNode(IsSync) -> run(), - esScanner:swSyncNode(IsSync), + esSyncSrv:swSyncNode(IsSync), ok. curInfo() -> - esScanner:curInfo(). + esSyncSrv:curInfo(). setLog(Val) -> - esScanner:setLog(Val). + esSyncSrv:setLog(Val). getLog() -> - esScanner:getLog(). + esSyncSrv:getLog(). getOnsync() -> - esScanner:getOnsync(). + esSyncSrv:getOnsync(). setOnsync() -> - esScanner:setOnsync(undefined). + esSyncSrv:setOnsync(undefined). setOnsync(Fun) -> - esScanner:setOnsync(Fun). + esSyncSrv:setOnsync(Fun). diff --git a/src/erlSync_sup.erl b/src/erlSync_sup.erl index e72eb20..c298203 100644 --- a/src/erlSync_sup.erl +++ b/src/erlSync_sup.erl @@ -23,5 +23,5 @@ start_link() -> %% modules => modules()} % optional init([]) -> SupFlags = #{strategy => one_for_one, intensity => 5, period => 10}, - ChildSpecs = [?ChildSpec(esScanner, worker)], + ChildSpecs = [?ChildSpec(esSyncSrv, worker)], {ok, {SupFlags, ChildSpecs}}. diff --git a/src/sync/esScanner.erl b/src/sync/esScanner.erl deleted file mode 100644 index 6cd6d09..0000000 --- a/src/sync/esScanner.erl +++ /dev/null @@ -1,755 +0,0 @@ --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, - handleAfter/3, - 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 - , sockMod = undefined - , sock = undefined -}). - -%% ************************************ 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 :: waiting | 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, waiting, #state{}, {doAfter, 0}}. - -handleAfter(_, waiting, State) -> - %% 启动tcp 异步监听 然后启动文件同步应用 启动定时器 等待建立连接 超时 就表示文件同步应用启动失败了 报错 - ListenPort = ?esCfgSync:getv(?listenPort), - case gen_tcp:listen(ListenPort, ?TCP_DEFAULT_OPTIONS) of - {ok, LSock} -> - case prim_inet:async_accept(LSock, -1) of - {ok, _Ref} -> - {ok, SockMod} = inet_db:lookup_socket(LSock), - io:format("IMY******************11111"), - spawn(fun() -> case os:type() of - {win32, _Osname} -> - os:cmd("start ./priv/fileSync.exe ./ " ++ integer_to_list(ListenPort)); - _ -> - os:cmd("./priv/fileSync ./ " ++ integer_to_list(ListenPort)) - end end), - io:format("IMY******************22222 "), - {kpS, State#state{sockMod = SockMod}, {sTimeout, 2000000, waitConnOver}}; - {error, Reason} -> - Msg = io_lib:format("init prim_inet:async_accept error ~p~n", [Reason]), - esUtils:logErrors(Msg), - {kpS, State, {sTimeout, 2000, waitConnOver}} - end; - {error, Reason} -> - Msg = io_lib:format("failed to listen on ~p - ~p (~s) ~n", [ListenPort, Reason, inet:format_error(Reason)]), - esUtils:logErrors(Msg), - {kpS, State, {sTimeout, 2000, waitConnOver}} - end. - -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) -> - kpS_S. - -handleCast(miPause, _, State) -> - {nextS, pause, State}; -handleCast(miUnpause, _, State) -> - {nextS, running, State}; -handleCast(miCollMods, running, State) -> - AllModules = (erlang:loaded() -- esUtils:getSystemModules()), - LastCollMods = filterCollMods(AllModules), - Time = ?esCfgSync:getv(?moduleTime), - {kpS, 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), - {kpS, 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), - {kpS, 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), - {kpS, 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), - {kpS, 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), - {kpS, State#state{hrlFileTimes = HrlFileTimeList}, [?gTimeout(miCompareHrlFiles, Time)]}; -handleCast({miSyncNode, IsSync}, _, State) -> - case IsSync of - true -> - {kpS, State#state{swSyncNode = true}}; - _ -> - {kpS, State#state{swSyncNode = false}} - end; -handleCast(_Msg, _, _State) -> - kpS_S. - -handleInfo({inet_async, LSock, _Ref, Msg}, _, #state{sockMod = SockMod} = State) -> - io:format("IMY************** get inet_async msg ~p~n", [Msg]), - case Msg of - {ok, Sock} -> - %% make it look like gen_tcp:accept - inet_db:register_socket(Sock, SockMod), - io:format("IMY************** socket optinon111 ~p~n", [inet:getopts(Sock, [active, packet])]), - io:format("IMY************** socket optinon222 ~p~n", [inet:getopts(LSock, [active, packet])]), - inet:setopts(Sock, [{active, true}]), - io:format("IMY************** socket optinon111 ~p~n", [inet:getopts(Sock, [active, packet])]), - prim_inet:async_accept(LSock, -1), - {nextS, running, State#state{sock = Sock}}; - {error, closed} -> - Msg = io_lib:format("error, closed listen sock error ~p~n",[closed]), - esUtils:logErrors(Msg), - {stop, normal}; - {error, Reason} -> - Msg = io_lib:format("listen sock error ~p~n",[Reason]), - esUtils:logErrors(Msg), - {stop, {lsock, Reason}} - end; -handleInfo({tcp, Socket, Data}, running, State) -> - io:format("IMY************** get tcp msg ~p~n", [Data]), - kpS_S; -handleInfo({tcp_closed, Socket}, running, State) -> - io:format("IMY************** get tcp msg ~p~n", [close]), - kpS_S; -handleInfo({tcp_error, Socket, Reason},running, State) -> - io:format("IMY************** get tcp msg ~p~n", [111]), - kpS_S; -handleInfo(_Msg, _, _State) -> - io:format("IMY************** get tcp msg ~p~n", [_Msg]), - kpS_S. - -handleOnevent({doSync, _}, Msg, Status, State) -> - handleCast(Msg, Status, State); -handleOnevent(sTimeout, waitConnOver, Status, State) -> - stop; -handleOnevent(_EventType, _EventContent, _Status, _State) -> - kpS_S. - -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 ********************************************* - diff --git a/src/sync/esSyncSrv.erl b/src/sync/esSyncSrv.erl new file mode 100644 index 0000000..427e400 --- /dev/null +++ b/src/sync/esSyncSrv.erl @@ -0,0 +1,235 @@ +-module(esSyncSrv). +-behaviour(gen_ipc). + +-include("erlSync.hrl"). + +-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, + handleAfter/3, + handleCast/3, + handleInfo/3, + handleOnevent/4, + terminate/3 +]). + +-define(SERVER, ?MODULE). +-define(None, 0). + +-record(state, { + srcFiles = #{} :: map() + , onsyncFun = undefined + , swSyncNode = false + , sockMod = undefined + , sock = undefined +}). + +%% ************************************ API start *************************** +rescan() -> + gen_ipc:cast(?SERVER, miRescan), + esUtils:logSuccess("start rescaning 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), + esUtils:loadCfg(), + esUtils:logSuccess("Console Notifications Enabled"), + ok; +setLog(_) -> + esUtils:setEnv(log, none), + esUtils: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, ?None, []). + +%% status :: waiting | running | pause +init(_Args) -> + erlang:process_flag(trap_exit, true), + esUtils:loadCfg(), + {ok, waiting, #state{}, {doAfter, ?None}}. + +handleAfter(?None, waiting, State) -> + %% 启动tcp 异步监听 然后启动文件同步应用 启动定时器 等待建立连接 超时 就表示文件同步应用启动失败了 报错 + ListenPort = ?esCfgSync:getv(?listenPort), + case gen_tcp:listen(ListenPort, ?TCP_DEFAULT_OPTIONS) of + {ok, LSock} -> + case prim_inet:async_accept(LSock, -1) of + {ok, _Ref} -> + {ok, SockMod} = inet_db:lookup_socket(LSock), + spawn(fun() -> + case os:type() of + {win32, _Osname} -> + os:cmd("start ./priv/fileSync.exe ./ " ++ integer_to_list(ListenPort)); + _ -> + os:cmd("./priv/fileSync ./ " ++ integer_to_list(ListenPort)) + end end), + {kpS, State#state{sockMod = SockMod}, {sTimeout, 4000, waitConnOver}}; + {error, Reason} -> + Msg = io_lib:format("init prim_inet:async_accept error ~p~n", [Reason]), + esUtils:logErrors(Msg), + {kpS, State, {sTimeout, 2000, waitConnOver}} + end; + {error, Reason} -> + Msg = io_lib:format("failed to listen on ~p - ~p (~s) ~n", [ListenPort, Reason, inet:format_error(Reason)]), + esUtils:logErrors(Msg), + {kpS, State, {sTimeout, 2000, waitConnOver}} + end. + +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) -> + kpS_S. + +handleCast(miPause, _, State) -> + {nextS, pause, State}; +handleCast(miUnpause, _, 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) -> + SrcFiles = esUtils:collSrcFiles(false), + {kpS_S, State#state{srcFiles = SrcFiles}}; +handleCast(_Msg, _, _State) -> + kpS_S. + +handleInfo({inet_async, LSock, _Ref, Msg}, _, #state{sockMod = SockMod} = State) -> + case Msg of + {ok, Sock} -> + %% make it look like gen_tcp:accept + inet_db:register_socket(Sock, SockMod), + inet:setopts(Sock, [{active, true}]), + prim_inet:async_accept(LSock, -1), + + %% 建立了连接 先发送监听目录配置 + {AddSrcDirs, OnlySrcDirs, DelSrcDirs} = esUtils:mergeExtraDirs(false), + AddStr = string:join([filename:nativename(OneDir) || OneDir <- AddSrcDirs], "|"), + OnlyStr = string:join([filename:nativename(OneDir) || OneDir <- OnlySrcDirs], "|"), + DelStr = string:join([filename:nativename(OneDir) || OneDir <- DelSrcDirs], "|"), + AllStr = string:join([AddStr, OnlyStr, DelStr], "\r\n"), + gen_tcp:send(Sock, AllStr), + case ?esCfgSync:getv(?compileCmd) of + undefined -> + %% 然后收集一下监听目录下的src文件 + SrcFiles = esUtils:collSrcFiles(true), + {nextS, running, State#state{sock = Sock, srcFiles = SrcFiles}}; + _ -> + {nextS, running, State} + end; + {error, closed} -> + Msg = io_lib:format("error, closed listen sock error ~p~n", [closed]), + esUtils:logErrors(Msg), + {stop, normal}; + {error, Reason} -> + Msg = io_lib:format("listen sock error ~p~n", [Reason]), + esUtils:logErrors(Msg), + {stop, {lsock, Reason}} + end; +handleInfo({tcp, _Socket, Data}, running, #state{srcFiles = SrcFiles, onsyncFun = OnsyncFun, swSyncNode = SwSyncNode} = State) -> + FileList = binary:split(Data, <<"\r\n">>, [global]), + %% 收集改动了beam hrl src 文件 然后执行相应的逻辑 + {Beams, Hrls, Srcs} = esUtils:dealChangeFile(FileList, [], [], []), + esUtils:reloadChangedMod(Beams, SwSyncNode, OnsyncFun, []), + case ?esCfgSync:getv(?compileCmd) of + undefined -> + esUtils:recompileChangeHrlFile(Hrls, SrcFiles, SwSyncNode), + esUtils:recompileChangeSrcFile(Srcs, SwSyncNode), + NewSrcFiles = esUtils:addNewFile(Srcs, SrcFiles), + {kpS, State#state{srcFiles = NewSrcFiles}}; + CmdStr -> + case Srcs =/= [] orelse Hrls =/= [] of + true -> + RetStr = os:cmd(CmdStr), + RetList = string:split(RetStr, "\n", all), + CmdMsg = io_lib:format("compile cmd:~p ~n", [CmdStr]), + esUtils:logSuccess(CmdMsg), + RetMsg = io_lib:format("the result: ~n ", []), + esUtils:logSuccess(RetMsg), + [ + begin + OneMsg = io_lib:format("~p ~n", [OneRet]), + esUtils:logSuccess(OneMsg) + end || OneRet <- RetList, OneRet =/= [] + ], + ok; + _ -> + ignore + end, + kpS_S + end; +handleInfo({tcp_closed, _Socket}, running, _State) -> + Msg = io_lib:format("esSyncSrv receive tcp_closed ~n", []), + esUtils:logErrors(Msg), + kpS_S; +handleInfo({tcp_error, _Socket, Reason}, running, _State) -> + Msg = io_lib:format("esSyncSrv receive tcp_error Reason:~p ~n", [Reason]), + esUtils:logErrors(Msg), + kpS_S; +handleInfo(_Msg, _, _State) -> + Msg = io_lib:format("esSyncSrv receive unexpect msg:~p ~n", [_Msg]), + esUtils:logErrors(Msg), + kpS_S. + +handleOnevent(sTimeout, waitConnOver, Status, State) -> + Msg = io_lib:format("failed to connect the fileSync to stop stauts:~p state:~p ~n", [Status, State]), + esUtils:logErrors(Msg), + stop; +handleOnevent(_EventType, _EventContent, _Status, _State) -> + kpS_S. + +terminate(_Reason, _Status, _State) -> + ok. \ No newline at end of file diff --git a/src/sync/esUtils.erl b/src/sync/esUtils.erl index 2b4c268..1415627 100644 --- a/src/sync/esUtils.erl +++ b/src/sync/esUtils.erl @@ -1,24 +1,10 @@ -module(esUtils). + -include("erlSync.hrl"). -compile(inline). -compile({inline_size, 128}). - --export([ - getModSrcDir/1, - getModOptions/1, - getFileType/1, - getSrcDir/1, - wildcard/2, - getEnv/2, - setEnv/2, - load/2, - logSuccess/1, - logErrors/1, - logWarnings/1, - getSystemModules/0, - tryGetModOptions/1 -]). +-compile([export_all, nowarn_export_all]). getModSrcDir(Module) -> case code:is_loaded(Module) of @@ -102,6 +88,20 @@ tryGetModOptions(Module) -> undefiend end. +tryGetSrcOptions(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; + _ -> + case NewDirName =/= SrcDir of + true -> + tryGetSrcOptions(NewDirName); + _ -> + undefined + end + end. transformOutdir(BeamDir, Options) -> [{outdir, BeamDir} | proplists:delete(outdir, Options)]. @@ -144,11 +144,16 @@ getFileType(Module) when is_atom(Module) -> Source = proplists:get_value(source, Props, ""), getFileType(Source); -getFileType(Source) when is_list(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; @@ -193,8 +198,8 @@ determineIncludeDirFromBeamDir(IncludeBase, IncludeDir, BeamDir) -> %% 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(".", _, _) -> undefined; +findIncludeDirFromAncestors("", _, _) -> undefined; findIncludeDirFromAncestors(Dir, Cwd, IncludeBase) -> NewDirName = filename:dirname(Dir), AttemptDir = filename:join(NewDirName, IncludeBase), @@ -263,9 +268,120 @@ getSrcDir(Dir, Ctr) -> true -> getSrcDir(filename:dirname(Dir), Ctr - 1) end. -%% Return all files in a directory matching a regex. -wildcard(Dir, Regex) -> - filelib:fold_files(Dir, Regex, true, fun(Y, Acc) -> [Y | Acc] end, []). +mergeExtraDirs(IsAddPath) -> + case ?esCfgSync:getv(?extraDirs) of + undefined -> + {[], [], []}; + ExtraList -> + FunMerge = + fun(OneExtra, {AddDirs, OnlyDirs, DelDirs} = AllAcc) -> + case OneExtra of + {add, 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 ++ AddDirs); + {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(2, AllAcc, Onlys ++ OnlyDirs); + {del, DirsAndOpts} -> + Dels = + [ + begin + filename:absname(Dir) + end || {Dir, _Opts} <- DirsAndOpts + ], + setelement(3, AllAcc, Dels ++ DelDirs) + end + end, + lists:foldl(FunMerge, {[], [], []}, ExtraList) + end. + +collSrcFiles(IsAddPath) -> + {AddSrcDirs, OnlySrcDirs, DelSrcDirs} = mergeExtraDirs(IsAddPath), + CollFiles = filelib:fold_files(filename:absname("./"), ".*\\.(erl|dtl|lfe|ex)$", true, + fun(OneFiles, Acc) -> + case isOnlyDir(OnlySrcDirs, OneFiles) of + true -> + case isDelDir(DelSrcDirs, OneFiles) of + false -> + SrcDir = list_to_binary(filename:dirname(OneFiles)), + case getOptions(SrcDir) of + undefined -> + Mod = list_to_atom(filename:basename(OneFiles, filename:extension(OneFiles))), + {ok, Options} = getModOptions(Mod), + setOptions(SrcDir, Options); + _ -> + ignore + end, + Acc#{list_to_binary(OneFiles) => 1}; + _ -> + Acc + end; + _ -> + Acc + end + end, #{}), + + FunCollAdds = + fun(OneDir, FilesAcc) -> + filelib:fold_files(OneDir, ".*\\.(erl|dtl|lfe|ex)$", true, fun(OneFiles, Acc) -> + Acc#{list_to_binary(OneFiles) => 1} end, FilesAcc) + end, + lists:foldl(FunCollAdds, CollFiles, AddSrcDirs). + +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(erlSync, Var) of @@ -288,7 +404,7 @@ logWarnings(Message) -> canLog(warnings) andalso error_logger:warning_msg(lists:flatten(Message)). canLog(MsgType) -> - case esScanner:getLog() of + case esSyncSrv:getLog() of true -> true; all -> true; none -> false; @@ -298,25 +414,6 @@ canLog(MsgType) -> _ -> false end. -%% Return a list of all modules that belong to Erlang rather than whatever application we may be running. -getSystemModules() -> - Apps = [ - appmon, asn1, common_test, compiler, crypto, debugger, - dialyzer, docbuilder, edoc, erl_interface, erts, et, - eunit, gs, hipe, inets, inets, inviso, jinterface, kernel, - mnesia, observer, orber, os_mon, parsetools, percept, pman, - reltool, runtime_tools, sasl, snmp, ssl, stdlib, syntax_tools, - test_server, toolbar, tools, tv, webtool, wx, xmerl, zlib, rebar, rebar3 - ], - FAppMod = - fun(App) -> - case application:get_key(App, modules) of - {ok, Modules} -> Modules; - _Other -> [] - end - end, - lists:flatten([FAppMod(X) || X <- Apps]). - %% 注意 map类型的数据不能当做key -type key() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple(). -type value() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple() | map(). @@ -354,3 +451,329 @@ lookup_clauses([], 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, esUtils:getEnv(Key, DefVal)} || {Key, DefVal} <- ?CfgList], + esUtils:load(?esCfgSync, KVs). + +%% ******************************* 加载与编译相关 ********************************************************************** +errorNoFile(Module) -> + Msg = io_lib:format("~p Couldn't load module: nofile", [Module]), + esUtils:logWarnings([Msg]). + +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. + +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). + +reloadChangedMod([], _SwSyncNode, OnsyncFun, Acc) -> + fireOnsync(OnsyncFun, Acc); +reloadChangedMod([Module | LeftMod], 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(LeftMod, 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(LeftMod, SwSyncNode, OnsyncFun, [Module | Acc]) + end. + +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([], _SwSyncNode) -> + ok; +recompileChangeSrcFile([File | LeftFile], SwSyncNode) -> + recompileSrcFile(File, SwSyncNode), + recompileChangeSrcFile(LeftFile, SwSyncNode). + +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([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). + +getCompileFunAndModuleName(SrcFile) -> + case esUtils: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) -> + 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} -> + 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; + {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; + _ -> + ignore + end. + +recompileSrcFile(SrcFile, SwSyncNode) -> + %% Get the module, src dir, and options... + SrcDir = filename:dirname(SrcFile), + {CompileFun, Module} = getCompileFunAndModuleName(SrcFile), + {OldBinary, Filename} = getObjectCode(Module), + case getOptions(SrcDir) of + {ok, Options} -> + RightFileDir = binary_to_list(filename:join(SrcDir, filename:basename(SrcFile))), + case CompileFun(RightFileDir, [binary, return | Options]) of + {ok, Module, Binary, Warnings} -> + printResults(Module, RightFileDir, [], Warnings), + reloadIfNecessary(Module, OldBinary, Binary, Filename), + {ok, [], Warnings}; + {ok, [{ok, Module, Binary, Warnings}], Warnings2} -> + printResults(Module, RightFileDir, [], Warnings ++ Warnings2), + reloadIfNecessary(Module, OldBinary, Binary, Filename), + {ok, [], Warnings ++ Warnings2}; + {ok, multiple, Results, Warnings} -> + printResults(Module, RightFileDir, [], Warnings), + [reloadIfNecessary(CompiledModule, OldBinary, Binary, Filename) || {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} + end; + undefined -> + case esUtils:tryGetModOptions(Module) of + {ok, Options} -> + setOptions(SrcDir, Options), + recompileSrcFile(SrcFile, SwSyncNode); + _ -> + case esUtils:tryGetSrcOptions(SrcDir) of + {ok, _Options} -> + recompileSrcFile(SrcFile, SwSyncNode); + _ -> + Msg = io_lib:format("Unable to determine options for ~s", [SrcFile]), + esUtils:logErrors(Msg) + end + end + end. + +recompileChangeHrlFile([], _SrcFiles, _SwSyncNode) -> + ok; +recompileChangeHrlFile([Hrl | LeftHrl], SrcFiles, SwSyncNode) -> + WhoInclude = whoInclude(Hrl, SrcFiles), + [recompileSrcFile(SrcFile, SwSyncNode) || {SrcFile, _} <- maps:to_list(WhoInclude)], + recompileChangeHrlFile(LeftHrl, SrcFiles, SwSyncNode). + +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). + +dealChangeFile([], Beams, Hrls, Srcs) -> + {Beams, Hrls, Srcs}; +dealChangeFile([OneFile | LeftFile], Beams, Hrls, Srcs) -> + case filename:extension(OneFile) of + <<".beam">> -> + Module = binary_to_atom(filename:basename(OneFile, <<".beam">>)), + dealChangeFile(LeftFile, [Module | Beams], Hrls, Srcs); + <<".hrl">> -> + dealChangeFile(LeftFile, Beams, [OneFile | Hrls], Srcs); + <<>> -> + dealChangeFile(LeftFile, Beams, Hrls, Srcs); + _ -> + dealChangeFile(LeftFile, Beams, Hrls, [OneFile | Srcs]) + end. + +addNewFile([], SrcFiles) -> + SrcFiles; +addNewFile([OneFile | LeftFile], SrcFiles) -> + case SrcFiles of + #{OneFile := _value} -> + addNewFile(LeftFile, SrcFiles); + _ -> + addNewFile(LeftFile, SrcFiles#{OneFile => 1}) + end. \ No newline at end of file