diff --git a/README.md b/README.md index 8ffef73..a238f13 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,40 @@ -erlSync -===== - -An OTP application - -Build ------ - - $ rebar3 compile +# erlSync +改造自 [sync](https://github.com/rustyio/sync) 有兴趣可以了解一下, 本项目仅仅在此基础做了一些封装改动和优化,工作原理并无差别 +# 工作原理 + 启动后,Sync会收集有关已加载模块,ebin目录,源文件,编译选项等的信息。 + 然后,Sync会定期检查源文件的最后修改日期。如果自上次扫描以来文件已更改,则Sync会使用上一组编译选项自动重新编译模块。如果编译成功,它将加载更新的模块。否则,它将编译错误输出到控制台。 + 同步还会定期检查任何梁文件的上次修改日期,如果文件已更改,则会自动重新加载。 + 扫描过程会在运行的Erlang VM上增加1%到2%的CPU负载。已经采取了很多措施将其保持在较低水平。 + 但这仅适用于开发模式,请勿在生产中运行。 + +# 使用 + 启动自动编译与加载 + erlSync:run(). + 暂停自动编译与加载 + erlSync:pause(). + 启动或者关闭集群同步加载 + erlSync:swSyncNode(TrueOrFalse). + 设置编译与加载日志提示 + erlSync:setLog(Val). + 设置加载后的钩子函数(支持匿名函数, {Mod, Fun}(Fun函数只有一个参数)格式, 以及他们的列表组合) + erlSync:setOnsync(FunOrFuns). + +# 配置说明 + 参见erlSync.sample.config + 默认配置为 + [ + {erlSync, + [ + {moduleTime, 30000}, + {srcDirTime, 5000}, + {srcFileTime, 5000}, + {compareBeamTime, 3000}, + {compareSrcFileTime, 3000}, + {srcDirs, undefined} + {log, all}, + {descendant, fix}, + {onlyMods, []}, + {excludedMods, []} + ] + } + ] \ No newline at end of file diff --git a/erlSync.sample.config b/erlSync.sample.config index 8652596..7d372df 100644 --- a/erlSync.sample.config +++ b/erlSync.sample.config @@ -1,61 +1,44 @@ [ {erlSync, [ - {moduleTime, 10000}, - - {srcDirTime, 10000}, - + %% 扫描module的间隔时间 + {moduleTime, 30000}, + %% 扫描 源码目录的间隔时间 + {srcDirTime, 5000}, + %% 扫描 源码文件的间隔时间 {srcFileTime, 5000}, - - {compareBeamTime, 2000}, - + %% 对比加载beam 文件的间隔时间 + {compareBeamTime, 3000}, + %% 对比编译erl hrl文件的间隔时间 {compareSrcFileTime, 1000}, - {file_variables, "-*- mode: compilation; mode: auto-revert; buffer-read-only: true; auto-revert-interval: 0.5 -*-\n\n"}, - - %% Temp file to write output messages. - {out_file, "/tmp/erlSync.out"}, - - %% List of modules to be excluded from scanning. While using rebar - %% it's not very useful to specify the excludes here as every - %% get/update-deps will override the settings. Instead specify - %% excluded stuff in the node's config file. - {excluded_modules, []}, - - %% log: Console notifications - %% valid values: all | none | [success | warnings | errors] - %% default: all + %% 编译和加载以及其他一些日志的提示级别设置 + %% 有效值: all | none | [success | warnings | errors] + %% 默认值: all {log, all}, - %% non_descendants: How to handle beams whose original source path is - %% not a descendant of the current working directory. - %% - %% valid values: fix | allow | ignore - %% * fix = attempt to find source files under current directory - %% * allow = don't do anything special, use the non-descendant path and - %% watch that file - %% * ignore = don't watch that module at all and ignore any changes to - %% its source path - %% default: fix - {non_descendants, fix}, - - %% whitelisted_modules: erlSync only these modules - %% default: [] - {whitelisted_modules, []}, - - %% excluded_modules: Ignore any modules listed - %% default: [] - {excluded_modules, []}, - - %% executable: Identify the program that you want run by the "growl" notifications - %% valid values: auto | notifu | 'notify-send' | growlnotify | emacsclient | notification_center - %% * auto = allow erlSync to autodetect which program to run - %% * growlnotify = Use Growl for Mac - %% * notification_center = Use OSX Notification Center - %% * 'notify-send' = Use libnotify for Linux - %% * notifu = The notifu program for Windows - %% * emacsclient = Emacs notifications - %% default: auto - {executable, auto} + %% 这个参数用于设置特殊目录下的文件检查编译与加载 + %% 格式:{srcDirs, {strategy(), [srcDirDescr()]}} | {srcDirs, undefined} + %% -type strategy() :: add | only. + %% 如果 strategy() is only, 仅仅扫描指定目录下的文件编译与加载. 如果 strategy() is add, 会扫描添加的指定目录同步编译与加载. + %% -type srcDirDescr() :: { Dir :: file:filename(), [Options :: compile_option()]}. + %% 默认值:undefined 根据当前工作目录 和 已经加载的模块做来得出需要扫描的目录 + {srcDirs, {strategy(), [srcDirDescr()]}}, + + %% 这个参数用来设置 怎么处理 当beam文件的源文件目录不是当前工作的子目录时的情况 + %% 有效值: fix | allow | ignore + %% * fix = 尝试在当前目录下查找源文件 + %% * allow = 不要做任何特别的事情,使用beam源文件原始路径查找该文件 + %% * ignore = 而忽略对其源路径的任何更改 + %% 默认值: fix + {descendant, fix}, + + %% 仅仅同步编译和加载该参数指定的模块 + %% default: [] 为空的时候 该参数无效 + {onlyMods, []}, + + %% 不同步编译和加载该参数指定的模块 + %% default: [] 为空的时候 该参数无效 + {excludedMods, []} ]} ]. diff --git a/include/erlSync.hrl b/include/erlSync.hrl new file mode 100644 index 0000000..e1ded85 --- /dev/null +++ b/include/erlSync.hrl @@ -0,0 +1,19 @@ +-define(LOG_ON(Val), Val == true; Val == all; Val == skip_success; is_list(Val), Val =/= []). + +-define(gTimeout(Type, Time), {{gTimeout, Type}, Time, Type}). + +-define(Log, log). +-define(moduleTime, moduleTime). +-define(srcDirTime, srcDirTime). +-define(srcFileTime, srcFileTime). +-define(compareBeamTime, compareBeamTime). +-define(compareSrcFileTime, compareSrcFileTime). +-define(srcDirs, srcDirs). +-define(onlyMods, onlyMods). +-define(excludedMods, excludedMods). +-define(descendant, descendant). +-define(CfgList, [{?Log, all}, {?moduleTime, 30000}, {?srcDirTime, 5000}, {?srcFileTime, 5000}, {?compareBeamTime, 3000}, {?compareSrcFileTime, 3000}, {?srcDirs, undefined}, {?onlyMods, []}, {?excludedMods, []}, {?descendant, fix}]). + +-define(esCfgSync, esCfgSync). + +-define(esRecompileCnt, '$esRecompileCnt'). \ No newline at end of file diff --git a/src/erlSync.app.src b/src/erlSync.app.src index f7dc350..b10828e 100644 --- a/src/erlSync.app.src +++ b/src/erlSync.app.src @@ -4,23 +4,8 @@ {registered, []}, {mod, {erlSync_app, []}}, {applications, [kernel, stdlib, syntax_tools, compiler]}, - {env, [ - {moduleTime, 10000}, - {srcDirTime, 10000}, - {srcFileTime, 5000}, - {compareBeamTime, 2000}, - {compareSrcFileTime, 1000}, - %% http://www.gnu.org/software/emacs/manual/html_node/emacs/File-Variables.html#File-Variables - {file_variables, "-*- mode: compilation; mode: auto-revert; buffer-read-only: true; auto-revert-interval: 0.5 -*-\n\n"}, - %% Temp file to write output messages. - {out_file, "/tmp/erlSync.out"}, - %% List of modules to be excluded from scanning. While using rebar - %% it's not very useful to specify the excludes here as every - %% get/update-deps will override the settings. Instead specify - %% excluded stuff in the node's config file. - {excluded_modules, []} - ]}, + {env, []}, {modules, []}, - {licenses, ["Apache 2.0"]}, + {licenses, ["MIT License"]}, {links, []} ]}. diff --git a/src/erlSync.erl b/src/erlSync.erl index 7842317..110cbed 100644 --- a/src/erlSync.erl +++ b/src/erlSync.erl @@ -5,16 +5,15 @@ stop/0, run/0, pause/0, - info/0, - log/1, - log/0, - onsync/0, - onsync/1, + curInfo/0, + setLog/1, + getLog/0, + getOnsync/0, + setOnsync/0, + setOnsync/1, swSyncNode/1 ]). --define(VALID_GROWL_OR_LOG(X), is_boolean(X); is_list(X); X == all; X == none; X == skip_success). - start() -> application:ensure_all_started(erlSync). @@ -27,8 +26,8 @@ run() -> esScanner:unpause(), esScanner:rescan(), ok; - {error, _Reason} -> - io:format("Err") + {error, Reason} -> + esUtils:logErrors("start erlSync error ~p~n", [Reason]) end. pause() -> @@ -39,21 +38,24 @@ swSyncNode(IsSync) -> esScanner:swSyncNode(IsSync), ok. -info() -> - esScanner:info(). +curInfo() -> + esScanner:curInfo(). -log(Val) when ?VALID_GROWL_OR_LOG(Val) -> +setLog(Val) -> esScanner:setLog(Val). -log() -> +getLog() -> esScanner:getLog(). -onsync(Fun) -> - esScanner:setOnsync(Fun). - -onsync() -> +getOnsync() -> esScanner:getOnsync(). +setOnsync() -> + esScanner:setOnsync(undefined). + +setOnsync(Fun) -> + esScanner:setOnsync(Fun). + diff --git a/src/erlSync_app.erl b/src/erlSync_app.erl index 79ff835..417f7c2 100644 --- a/src/erlSync_app.erl +++ b/src/erlSync_app.erl @@ -1,10 +1,4 @@ -%%%------------------------------------------------------------------- -%% @doc erlSync public API -%% @end -%%%------------------------------------------------------------------- - -module(erlSync_app). - -behaviour(application). -export([start/2, stop/1]). @@ -14,5 +8,3 @@ start(_StartType, _StartArgs) -> stop(_State) -> ok. - -%% internal functions diff --git a/src/erlSync_sup.erl b/src/erlSync_sup.erl index 46b1d64..e72eb20 100644 --- a/src/erlSync_sup.erl +++ b/src/erlSync_sup.erl @@ -1,5 +1,4 @@ -module(erlSync_sup). - -behaviour(supervisor). -export([ @@ -23,6 +22,6 @@ start_link() -> %% type => worker(), % optional %% modules => modules()} % optional init([]) -> - SupFlags = #{strategy => rest_for_one, intensity => 1, period => 10}, - ChildSpecs = [?ChildSpec(esOptions, worker), ?ChildSpec(esScanner, worker)], + SupFlags = #{strategy => one_for_one, intensity => 5, period => 10}, + ChildSpecs = [?ChildSpec(esScanner, worker)], {ok, {SupFlags, ChildSpecs}}. diff --git a/src/sync/esScanner.erl b/src/sync/esScanner.erl index 770526b..648d574 100644 --- a/src/sync/esScanner.erl +++ b/src/sync/esScanner.erl @@ -1,4 +1,6 @@ -module(esScanner). +-include("erlSync.hrl"). + -behaviour(gen_ipc). -compile(inline). @@ -8,14 +10,14 @@ -export([ start_link/0, rescan/0, - info/0, - swSyncNode/1, pause/0, unpause/0, + setLog/1, + getLog/0, + curInfo/0, getOnsync/0, setOnsync/1, - setLog/1, - getLog/0 + swSyncNode/1 ]). %% gen_ipc callbacks @@ -29,45 +31,30 @@ ]). -define(SERVER, ?MODULE). --define(PRINT(Var), io:format("DEBUG: ~p:~p - ~p~n~n ~p~n~n", [?MODULE, ?LINE, ??Var, Var])). --define(LOG_OR_GROWL_ON(Val), Val == true;Val == all;Val == skip_success;is_list(Val), Val =/= []). --define(LOG_OR_GROWL_OFF(Val), Val == false;F == none;F == []). - --define(gTimeout(Type, Time), {{gTimeout, Type}, Time, Type}). - --define(esCfgSync, esCfgSync). - --define(Log, log). --define(moduleTime, moduleTime). --define(srcDirTime, srcDirTime). --define(srcFileTime, srcFileTime). --define(compareBeamTime, compareBeamTime). --define(compareSrcFileTime, compareSrcFileTime). --define(CfgList, [{?Log, all}, {?moduleTime, 30000}, {?srcDirTime, 5000}, {?srcFileTime, 5000}, {?compareBeamTime, 3000}, {?compareSrcFileTime, 3000}]). -type timestamp() :: file:date_time() | 0. - -record(state, { - modules = [] :: [module()], - srcDirs = [] :: [file:filename()], - hrlDirs = [] :: [file:filename()], - srcFiles = [] :: [file:filename()], - hrlFiles = [] :: [file:filename()], - beamTimes = undefined :: [{module(), timestamp()}] | undefined, - srcFileTimes = [] :: [{file:filename(), timestamp()}], - hrlFileTimes = [] :: [{file:filename(), timestamp()}], - onsyncFun = undefined, - patching = false + 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() -> - io:format("Scanning source files...~n"), 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), - gen_ipc:cast(?SERVER, miCompareHrlFiles), + esUtils:logSuccess("start Scanning source files..."), ok. unpause() -> @@ -75,24 +62,22 @@ unpause() -> ok. pause() -> - esUtils:log_success("Pausing erlSync. Call sync:go() to restart~n"), gen_ipc:cast(?SERVER, miPause), + esUtils:logSuccess("Pausing erlSync. Call erlSync:run() to restart"), ok. -info() -> - io:format("erlSync Info...~n"), - gen_ipc:call(?SERVER, miInfo), - ok. +curInfo() -> + gen_ipc:call(?SERVER, miCurInfo). -setLog(T) when ?LOG_OR_GROWL_ON(T) -> +setLog(T) when ?LOG_ON(T) -> esUtils:setEnv(log, T), loadCfg(), - esUtils:log_success("Console Notifications Enabled~n"), + esUtils:logSuccess("Console Notifications Enabled"), ok; -setLog(F) when ?LOG_OR_GROWL_OFF(F) -> +setLog(_) -> esUtils:setEnv(log, none), loadCfg(), - esUtils:log_success("Console Notifications Disabled~n"), + esUtils:logSuccess("Console Notifications Disabled"), ok. getLog() -> @@ -108,27 +93,29 @@ getOnsync() -> setOnsync(Fun) -> gen_ipc:call(?SERVER, {miSetOnsync, Fun}). - -%% status running | pause +%% ************************************ API end *************************** start_link() -> gen_ipc:start_link({local, ?SERVER}, ?MODULE, [], []). +%% status :: running | pause init([]) -> erlang:process_flag(trap_exit, true), - rescan(), - startup(), 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, _From) -> - OnSync = State#state.onsyncFun, +handleCall(miGetOnsync, _, #state{onsyncFun = OnSync} = State, _From) -> {reply, OnSync, State}; handleCall({miSetOnsync, Fun}, _, State, _From) -> - State2 = State#state{onsyncFun = Fun}, - {reply, ok, State2}; - -handleCall(miInfo, _, State, _Form) -> + {reply, ok, State#state{onsyncFun = Fun}}; +handleCall(miCurInfo, _, State, _Form) -> {reply, {erlang:get(), State}, State}; handleCall(_Request, _, _State, _From) -> keepStatusState. @@ -136,119 +123,81 @@ handleCall(_Request, _, _State, _From) -> handleCast(miPause, _, State) -> {nextStatus, pause, State}; handleCast(miUnpause, _, State) -> - rescan(), {nextStatus, running, State}; handleCast(miCollMods, running, State) -> - % Get a list of all loaded non-system modules. AllModules = (erlang:loaded() -- esUtils:getSystemModules()), - - % Delete excluded modules/applications - CollModules = collMods(AllModules), - - %% Schedule the next interval... + LastCollMods = filterCollMods(AllModules), Time = ?esCfgSync:getv(?moduleTime), - {keepStatus, State#state{modules = CollModules}, [?gTimeout(miCollMods, Time)]}; - -handleCast(miCollSrcDirs, running, State) -> + {keepStatus, State#state{modules = LastCollMods}, [?gTimeout(miCollMods, Time)]}; +handleCast(miCollSrcDirs, running, #state{modules = Modules} = State) -> {USortedSrcDirs, USortedHrlDirs} = - case application:get_env(erlSync, srcDirs) of + case ?esCfgSync:getv(srcDirs) of undefined -> - collSrcDirs(State, [], []); - {ok, {add, DirsAndOpts}} -> - collSrcDirs(State, dirs(DirsAndOpts), []); - {ok, {replace, DirsAndOpts}} -> - collSrcDirs(State, [], dirs(DirsAndOpts)) + 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) -> - %% For each source dir, get a list of source files... - FErl = - fun(X, Acc) -> - esUtils:wildcard(X, ".*\\.(erl|dtl|lfe|ex)$") ++ Acc +handleCast(miCollSrcFiles, running, #state{hrlDirs = HrlDirs, srcDirs = SrcDirs} = State) -> + FSrc = + fun(Dir, Acc) -> + esUtils:wildcard(Dir, ".*\\.(erl|dtl|lfe|ex)$") ++ Acc end, - ErlFiles = lists:usort(lists:foldl(FErl, [], State#state.srcDirs)), - - %% For each include dir, get a list of hrl files... + SrcFiles = lists:usort(lists:foldl(FSrc, [], SrcDirs)), FHrl = - fun(X, Acc) -> - esUtils:wildcard(X, ".*\\.hrl$") ++ Acc + fun(Dir, Acc) -> + esUtils:wildcard(Dir, ".*\\.hrl$") ++ Acc end, - HrlFiles = lists:usort(lists:foldl(FHrl, [], State#state.hrlDirs)), - - %% Schedule the next interval... + HrlFiles = lists:usort(lists:foldl(FHrl, [], HrlDirs)), Time = ?esCfgSync:getv(?srcFileTime), - %% Return with updated files... - {keepStatus, State#state{srcFiles = ErlFiles, hrlFiles = HrlFiles}, [?gTimeout(miCollSrcFiles, Time)]}; - -handleCast(miCompareBeams, running, #state{onsyncFun = OnsyncFun} = State) -> - %% Create a list of beam file lastmod times, but filter out modules not having - %% a valid beam file reference. - F = fun(X) -> - case code:which(X) of - Beam when is_list(Beam) -> - case filelib:last_modified(Beam) of - 0 -> - false; %% file not found - LastMod -> - {true, {X, LastMod}} - end; - _Other -> - false %% non_existing | cover_compiled | preloaded - end - end, - NewBeamLastMod = lists:usort(lists:filtermap(F, State#state.modules)), - - %% Compare to previous results, if there are changes, then reload - %% the beam... - reloadChangedMod(State#state.beamTimes, NewBeamLastMod, State#state.patching, OnsyncFun, []), - + {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) -> +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... - F = - fun(X) -> - LastMod = filelib:last_modified(X), - {X, LastMod} - end, - NewSrcFileLastMod = lists:usort([F(X) || X <- State#state.srcFiles]), - + 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(State#state.srcFileTimes, NewSrcFileLastMod, State#state.patching), - - %% Schedule the next interval... - + recompileChangeSrcFile(SrcFileTimes, NewSrcFileLastMod, 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 = NewSrcFileLastMod}, [?gTimeout(miCompareSrcFiles, Time)]}; - -handleCast(miCompareHrlFiles, running, State) -> +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... - F = - fun(X) -> - LastMod = filelib:last_modified(X), - {X, LastMod} - end, - NewHrlFileLastMod = lists:usort([F(X) || X <- State#state.hrlFiles]), - + 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(State#state.hrlFileTimes, NewHrlFileLastMod, State#state.srcFiles, State#state.patching), - - %% Schedule the next interval... + recompileChangeHrlFile(HrlFileTimes, NewHrlFileLastMod, 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 = NewHrlFileLastMod}, [?gTimeout(miCompareHrlFiles, Time)]}; - handleCast({miSyncNode, IsSync}, _, State) -> case IsSync of true -> - {keepStatus, State#state{patching = true}}; + {keepStatus, State#state{swSyncNode = true}}; _ -> - {keepStatus, State#state{patching = false}} + {keepStatus, State#state{swSyncNode = false}} end; - handleCast(_Msg, _, _State) -> keepStatusState. @@ -260,11 +209,15 @@ handleOnevent({gTimeout, _}, Msg, Status, State) -> handleOnevent(_EventType, _EventContent, _Status, _State) -> keepStatusState. +terminate(_Reason, _Status, _State) -> + ok. + +%% ***********************************PRIVATE FUNCTIONS start ******************************************* -dirs(DirsAndOpts) -> +addSrcDirs(DirsAndOpts) -> [ begin - %% ensure module out path exists & in our code list + %% ensure module out path exists & in our code path list case proplists:get_value(outdir, Opts) of undefined -> true; @@ -274,59 +227,48 @@ dirs(DirsAndOpts) -> end, setOptions(Dir, Opts), Dir - end || {Dir, Opts} <- DirsAndOpts]. - - -terminate(_Reason, _Status, _State) -> - ok. + end || {Dir, Opts} <- DirsAndOpts + ]. -%%% PRIVATE FUNCTIONS %%% -reloadChangedMod([{Module, LastMod} | T1], [{Module, LastMod} | T2], EnablePatching, OnsyncFun, Acc) -> - %% Beam hasn't changed, do nothing... - reloadChangedMod(T1, T2, EnablePatching, OnsyncFun, Acc); -reloadChangedMod([{Module, _} | T1], [{Module, _} | T2], EnablePatching, OnsyncFun, Acc) -> - %% Beam has changed, reload... +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~n", [Module]), - esUtils:log_errors(Msg), - reloadChangedMod(T1, T2, EnablePatching, OnsyncFun, Acc); + Msg = io_lib:format("Error loading object code for ~p", [Module]), + esUtils:logErrors(Msg), + reloadChangedMod(T1, T2, SwSyncNode, OnsyncFun, Acc); {Module, Binary, Filename} -> - code:load_binary(Module, Filename, Binary), - %% If patching is enabled, then reload the module across *all* connected - %% erlang VMs, and save the compiled beam to disk. - case EnablePatching of + 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} = syncLoadModOnAllNodes(Module), - Msg = io_lib:format("~s: Reloaded on ~p nodes! (Beam changed.)~n", [Module, NumNodes]), - esUtils:log_success(Msg); + {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 -> - %% Print a status message... - Msg = io_lib:format("~s: Reloaded! (Beam changed.)~n", [Module]), - esUtils:log_success(Msg) + ignore end, - reloadChangedMod(T1, T2, EnablePatching, OnsyncFun, [Module | Acc]) + reloadChangedMod(T1, T2, SwSyncNode, OnsyncFun, [Module | Acc]) end; - -reloadChangedMod([{Module1, _LastMod1} | T1] = OldLastMods, [{Module2, _LastMod2} | T2] = NewLastMods, EnablePatching, OnsyncFun, Acc) -> +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, EnablePatching, OnsyncFun, Acc); + reloadChangedMod(T1, NewLastMods, SwSyncNode, OnsyncFun, Acc); false -> - reloadChangedMod(OldLastMods, T2, EnablePatching, OnsyncFun, Acc) + reloadChangedMod(OldLastMods, T2, SwSyncNode, OnsyncFun, Acc) end; -reloadChangedMod(A, B, _EnablePatching, OnsyncFun, Acc) when A =:= []; B =:= [] -> - % MsgAdd = - % case EnablePatching of - % true -> " on " ++ integer_to_list(length(get_nodes())) ++ " nodes."; - % false -> "." - % end, - %% Done. +reloadChangedMod(A, B, _SwSyncNode, OnsyncFun, Acc) when A =:= []; B =:= [] -> fireOnsync(OnsyncFun, Acc), ok; reloadChangedMod(undefined, _Other, _, _, _) -> - %% First load, do nothing. ok. fireOnsync(OnsyncFun, Modules) -> @@ -348,58 +290,67 @@ 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. + %% Get a list of nodes known by this node, plus all attached nodes. Nodes = getNodes(), - io:format("[~s:~p] DEBUG - Nodes: ~p~n", [?MODULE, ?LINE, Nodes]), NumNodes = length(Nodes), - {Module, Binary, _} = code:get_object_code(Module), FSync = fun(Node) -> - io:format("[~s:~p] DEBUG - Node: ~p~n", [?MODULE, ?LINE, Node]), - Msg = io_lib:format("Reloading '~s' on ~s.~n", [Module, Node]), - esUtils:log_success(Msg), + io:format("[~s:~p] DEBUG - Node: ~p", [?MODULE, ?LINE, Node]), + Msg = io_lib:format("Reloading '~s' on ~s", [Module, Node]), + esUtils:logSuccess(Msg), 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]), - {module, Module} = rpc:call(Node, code, load_file, [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", [Node, Module]), + 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. - {module, Module} = rpc:call(Node, code, load_binary, [Module, undefined, Binary]) + 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", [Node, Module]), + 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}. - -recompileChangeSrcFile([{File, LastMod} | T1], [{File, LastMod} | T2], EnablePatching) -> - %% Beam hasn't changed, do nothing... - recompileChangeSrcFile(T1, T2, EnablePatching); -recompileChangeSrcFile([{File, _} | T1], [{File, _} | T2], EnablePatching) -> - %% File has changed, recompile... - recompileSrcFile(File, EnablePatching), - recompileChangeSrcFile(T1, T2, EnablePatching); -recompileChangeSrcFile([{File1, _LastMod1} | T1] = OldSrcFiles, [{File2, LastMod2} | T2] = NewSrcFiles, EnablePatching) -> + {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, EnablePatching); + recompileChangeSrcFile(T1, NewSrcFiles, SwSyncNode); false -> - maybeRecompileSrcFile(File2, LastMod2, EnablePatching), - recompileChangeSrcFile(OldSrcFiles, T2, EnablePatching) + maybeRecompileSrcFile(File2, LastMod2, SwSyncNode), + recompileChangeSrcFile(OldSrcFiles, T2, SwSyncNode) end; -recompileChangeSrcFile([], [{File, LastMod} | T2], EnablePatching) -> - maybeRecompileSrcFile(File, LastMod, EnablePatching), - recompileChangeSrcFile([], T2, EnablePatching); +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, _) -> - %% First load, do nothing. ok. erlydtlCompile(SrcFile, Options) -> @@ -429,20 +380,20 @@ lfe_compile(SrcFile, Options) -> Compiler = lfe_comp, Compiler:file(SrcFile, Options). -maybeRecompileSrcFile(File, LastMod, EnablePatching) -> +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, EnablePatching); + recompileSrcFile(File, SwSyncNode); _ -> ok end; _ -> %% File is new, recompile... - recompileSrcFile(File, EnablePatching) + recompileSrcFile(File, SwSyncNode) end. getCompileFunAndModuleName(SrcFile) -> @@ -463,12 +414,9 @@ getObjectCode(Module) -> _ -> undefined end. -reloadIfNecessary(_CompileFun, SrcFile, Module, Binary, Binary, _Options, Warnings) -> - %% Compiling didn't change the beam code. Don't reload... - printResults(Module, SrcFile, [], Warnings), - {ok, [], Warnings}; - -reloadIfNecessary(CompileFun, SrcFile, Module, _OldBinary, _Binary, Options, Warnings) -> +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... @@ -476,89 +424,66 @@ reloadIfNecessary(CompileFun, SrcFile, Module, _OldBinary, _Binary, Options, War {module, Module} -> ok; {error, nofile} -> errorNoFile(Module); {error, embedded} -> - %% Module is not yet loaded, load it. - case code:load_file(Module) of + case code:load_file(Module) of %% Module is not yet loaded, load it. {module, Module} -> ok; {error, nofile} -> errorNoFile(Module) end end, - gen_ipc:cast(?SERVER, miCompareBeams), - - %% Print the warnings... - printResults(Module, SrcFile, [], Warnings), - {ok, [], Warnings}. + atomics:add(persistent_term:get(?esRecompileCnt), 1, 1). errorNoFile(Module) -> - Msg = io_lib:format("~p:0: Couldn't load module: nofile~n", [Module]), - esUtils:log_warnings([Msg]). + Msg = io_lib:format("~p Couldn't load module: nofile", [Module]), + esUtils:logWarnings([Msg]). -recompileSrcFile(SrcFile, _EnablePatching) -> +recompileSrcFile(SrcFile, _SwSyncNode) -> %% Get the module, src dir, and options... case esUtils:getSrcDir(SrcFile) of {ok, SrcDir} -> {CompileFun, Module} = getCompileFunAndModuleName(SrcFile), - - %% Get the old binary code... 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, 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, Warnings ++ Warnings2); - + reloadIfNecessary(CompileFun, SrcFile, Module, OldBinary, Binary, Options), + printResults(Module, SrcFile, [], Warnings ++ Warnings2), + {ok, [], Warnings ++ Warnings2}; {ok, multiple, Results, Warnings} -> - Reloader = - fun({CompiledModule, Binary}) -> - {ok, _, _} = reloadIfNecessary(CompileFun, SrcFile, CompiledModule, OldBinary, Binary, Options, Warnings) - end, - lists:foreach(Reloader, Results), + [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} -> - %% Compiling failed. Print the warnings and errors... printResults(Module, SrcFile, Errors, Warnings), {ok, Errors, Warnings} end; undefined -> Msg = io_lib:format("Unable to determine options for ~p", [SrcFile]), - esUtils:log_errors(Msg) + esUtils:logErrors(Msg) end; _ -> Msg = io_lib:format("not find the file ~p", [SrcFile]), - esUtils:log_errors(Msg) + esUtils:logErrors(Msg) end. -printResults(Module, SrcFile, [], []) -> - Msg = io_lib:format("~s:0: Recompiled.~n", [SrcFile]), - case code:is_loaded(Module) of - {file, _} -> - ok; - false -> - ignore - end, - esUtils:log_success(lists:flatten(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:0: Recompiled with ~p warnings~n", [SrcFile, length(Warnings)]) - ], - esUtils:log_warnings(Msg); + 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:log_errors(Msg). + esUtils:logErrors(Msg). %% @private Print error messages in a pretty and user readable way. @@ -568,11 +493,12 @@ formatErrors(File, Errors, Warnings) -> AllWarnings1 = lists:sort(lists:flatten([X || {_, X} <- Warnings])), AllWarnings2 = [{Line, "Warning", Module, Description} || {Line, Module, Description} <- AllWarnings1], Everything = lists:sort(AllErrors2 ++ AllWarnings2), - F = fun({Line, Prefix, Module, ErrorDescription}) -> - Msg = formatError(Module, ErrorDescription), - io_lib:format("~s:~p: ~s: ~s~n", [File, Line, Prefix, Msg]) - end, - [F(X) || X <- Everything]. + 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 @@ -580,36 +506,32 @@ formatError(Module, ErrorDescription) -> false -> io_lib:format("~s", [ErrorDescription]) end. -recompileChangeHrlFile([{File, LastMod} | T1], [{File, LastMod} | T2], SrcFiles, Patching) -> - %% Hrl hasn't changed, do nothing... - recompileChangeHrlFile(T1, T2, SrcFiles, Patching); -recompileChangeHrlFile([{File, _} | T1], [{File, _} | T2], SrcFiles, Patching) -> - %% File has changed, recompile... +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, Patching) || SrcFile <- WhoInclude], - recompileChangeHrlFile(T1, T2, SrcFiles, Patching); -recompileChangeHrlFile([{File1, _LastMod1} | T1] = OldHrlFiles, [{File2, LastMod2} | T2] = NewHrlFiles, SrcFiles, Patching) -> + [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, Patching); + recompileChangeHrlFile(T1, NewHrlFiles, SrcFiles, SwSyncNode); false -> %% File is new, look for src that include it WhoInclude = whoInclude(File2, SrcFiles), - [maybeRecompileSrcFile(SrcFile, LastMod2, Patching) || SrcFile <- WhoInclude], - recompileChangeHrlFile(OldHrlFiles, T2, SrcFiles, Patching) + [maybeRecompileSrcFile(SrcFile, LastMod2, SwSyncNode) || SrcFile <- WhoInclude], + recompileChangeHrlFile(OldHrlFiles, T2, SrcFiles, SwSyncNode) end; -recompileChangeHrlFile([], [{File, LastMod} | T2], SrcFiles, Patching) -> - %% File is new, look for src that include it +recompileChangeHrlFile([], [{File, LastMod} | T2], SrcFiles, SwSyncNode) -> WhoInclude = whoInclude(File, SrcFiles), - [maybeRecompileSrcFile(SrcFile, LastMod, Patching) || SrcFile <- WhoInclude], - recompileChangeHrlFile([], T2, SrcFiles, Patching); -recompileChangeHrlFile([{File1, _LastMod1} | T1], [], SrcFiles, Patching) -> - %% Rest of file(s) removed, warn and process next + [maybeRecompileSrcFile(SrcFile, LastMod, SwSyncNode) || SrcFile <- WhoInclude], + recompileChangeHrlFile([], T2, SrcFiles, SwSyncNode); +recompileChangeHrlFile([{File1, _LastMod1} | T1], [], SrcFiles, SwSyncNode) -> warnDelHrlFiles(File1, SrcFiles), - recompileChangeHrlFile(T1, [], SrcFiles, Patching); + recompileChangeHrlFile(T1, [], SrcFiles, SwSyncNode); recompileChangeHrlFile([], [], _, _) -> %% Done ok; @@ -621,17 +543,18 @@ warnDelHrlFiles(HrlFile, SrcFiles) -> WhoInclude = whoInclude(HrlFile, SrcFiles), case WhoInclude of [] -> ok; - _ -> io:format( - "Warning. Deleted ~p file included in existing src files: ~p~n", - [filename:basename(HrlFile), lists:map(fun(File) -> filename:basename(File) end, WhoInclude)]) + _ -> + 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, + Pred = + fun(SrcFile) -> + {ok, Forms} = epp_dodger:parse_file(SrcFile), + isInclude(HrlFileBaseName, Forms) + end, lists:filter(Pred, SrcFiles). isInclude(_HrlFile, []) -> @@ -645,27 +568,23 @@ isInclude(HrlFile, [{tree, attribute, _, {attribute, _, [{_, _, IncludeFile}]}} isInclude(HrlFile, [_SomeForm | Forms]) -> isInclude(HrlFile, Forms). -collMods(Modules) -> - excludeMods(whitelistMods(Modules)). +filterCollMods(Modules) -> + excludeMods(onlyMods(Modules)). -whitelistMods(Modules) -> - case application:get_env(erlSync, whitelistMods) of - {ok, []} -> +onlyMods(Modules) -> + case ?esCfgSync:getv(?onlyMods) of + [] -> Modules; - {ok, WhitelistMods} -> - [Mod || Mod <- Modules, checkModIsMatch(Mod, WhitelistMods) == true]; - _ -> - Modules + OnlyMods -> + [Mod || Mod <- Modules, checkModIsMatch(OnlyMods, Mod) == true] end. excludeMods(Modules) -> - case application:get_env(erlSync, excludedMods) of - {ok, []} -> + case ?esCfgSync:getv(?excludedMods) of + [] -> Modules; - {ok, ExcludedModules} -> - [Mod || Mod <- Modules, checkModIsMatch(Mod, ExcludedModules) == false]; - _ -> - Modules + ExcludedModules -> + [Mod || Mod <- Modules, checkModIsMatch(ExcludedModules, Mod) == false] end. checkModIsMatch([], _Module) -> @@ -685,23 +604,17 @@ checkModIsMatch([ModOrPattern | T], Module) -> checkModIsMatch(T, Module) end. -collSrcDirs(State, ExtraDirs, ReplaceDirs) -> - %% Extract the compile / options / source / dir from each module. - F = +collSrcDirs(Modules, AddDirs, OnlyDirs) -> + FColl = fun - (X, {SrcAcc, HrlAcc} = Acc) -> - %% Get the dir... - case esUtils:getSrcDirFromMod(X) of + (Mod, {SrcAcc, HrlAcc} = Acc) -> + case esUtils:getModSrcDir(Mod) of {ok, SrcDir} -> - case isReplaceDir(SrcDir, ReplaceDirs) of + case isOnlyDir(OnlyDirs, SrcDir) of true -> - %% Get the options, storse under the dir... - {ok, Options} = esUtils:getOptionsFromMod(X), - %% Store the options for later reference... + {ok, Options} = esUtils:getModOptions(Mod), HrlDir = proplists:get_all_values(i, Options), - setOptions(SrcDir, Options), - %% Return the dir... {[SrcDir | SrcAcc], HrlDir ++ HrlAcc}; _ -> Acc @@ -710,28 +623,34 @@ collSrcDirs(State, ExtraDirs, ReplaceDirs) -> Acc end end, - {SrcDirs, HrlDirs} = lists:foldl(F, {ExtraDirs, []}, State#state.modules), + {SrcDirs, HrlDirs} = lists:foldl(FColl, {AddDirs, []}, Modules), USortedSrcDirs = lists:usort(SrcDirs), USortedHrlDirs = lists:usort(HrlDirs), - %% InitialDirs = sync_utils:initial_src_dirs(), - - %% Return with updated dirs... {USortedSrcDirs, USortedHrlDirs}. -isReplaceDir(_, []) -> +isOnlyDir([], _) -> true; -isReplaceDir(SrcDir, ReplaceDirs) -> - lists:foldl( - fun - (Dir, false) -> - case re:run(SrcDir, Dir) of - nomatch -> false; - _ -> true - end; - (_, Acc) -> Acc - end, false, ReplaceDirs). - -%% ***********************************misc fun start ******************************************* +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 -> @@ -749,11 +668,8 @@ setOptions(SrcDir, Options) -> erlang:put(SrcDir, NewOptions) end. -startup() -> - io:format("Growl notifications disabled~n"). - loadCfg() -> KVs = [{Key, esUtils:getEnv(Key, DefVal)} || {Key, DefVal} <- ?CfgList], esUtils:load(?esCfgSync, KVs). -%% ***********************************misc fun end ********************************************* +%% ***********************************PRIVATE FUNCTIONS end ********************************************* diff --git a/src/sync/esUtils.erl b/src/sync/esUtils.erl index 1d47996..843b74e 100644 --- a/src/sync/esUtils.erl +++ b/src/sync/esUtils.erl @@ -1,23 +1,25 @@ -module(esUtils). +-include("erlSync.hrl"). -compile(inline). -compile({inline_size, 128}). -export([ - getSrcDirFromMod/1, - getOptionsFromMod/1, + getModSrcDir/1, + getModOptions/1, getFileType/1, getSrcDir/1, wildcard/2, getEnv/2, setEnv/2, load/2, + logSuccess/1, + logErrors/1, + logWarnings/1, getSystemModules/0 ]). --compile([export_all, nowarn_export_all]). - -getSrcDirFromMod(Module) -> +getModSrcDir(Module) -> case code:is_loaded(Module) of {file, _} -> try @@ -27,17 +29,15 @@ getSrcDirFromMod(Module) -> %% Ensure that the file exists, is a decendent of the tree, and how to deal with that IsFile = filelib:is_regular(Source), IsDescendant = isDescendent(Source), - NonDescendant = getEnv(non_descendant, fix), + Descendant = ?esCfgSync:getv(?descendant), LastSource = - case {IsFile, IsDescendant, NonDescendant} of + case {IsFile, IsDescendant, Descendant} of %% is file and descendant, we're good to go {true, true, _} -> Source; %% is not a descendant, but we allow them, so good to go {true, false, allow} -> Source; - %% is not a file, but is a descendant, file is deleted, nothing we can do - {false, true, _} -> undefined; %% is not a descendant, and we fix non-descendants, so let's fix it - {_, false, fix} -> fixDescendantSource(Source, IsFile); + {_, false, fix} -> fixDescendantSource(Source); %% Anything else, and we don't know what to do, so let's just bail. _ -> undefined end, @@ -56,7 +56,7 @@ getSrcDirFromMod(Module) -> undefined end. -getOptionsFromMod(Module) -> +getModOptions(Module) -> case code:is_loaded(Module) of {file, _} -> try @@ -75,7 +75,7 @@ getOptionsFromMod(Module) -> {ok, Options6} catch ExType:Error:Stacktrace -> Msg = [io_lib:format("~p:0: ~p looking for options: ~p. Stack: ~p~n", [Module, ExType, Error, Stacktrace])], - log_warnings(Msg), + logWarnings(Msg), {ok, []} end; _ -> @@ -95,7 +95,7 @@ transformAllIncludes(Module, BeamDir, Options) -> [transformInclude(Module, BeamDir, Opt) || Opt <- Options]. transformInclude(Module, BeamDir, {i, IncludeDir}) -> - {ok, SrcDir} = getSrcDirFromMod(Module), + {ok, SrcDir} = getModSrcDir(Module), {ok, IncludeDir2} = determineIncludeDir(IncludeDir, BeamDir, SrcDir), {i, IncludeDir2}; transformInclude(_, _, Other) -> @@ -115,7 +115,7 @@ addFileType(Module, Options) -> Type = getFileType(Module), [{type, Type} | Options]. -%% @private This will check if the given module or source file is an ErlyDTL template. +%% This will check if the given module or source file is an ErlyDTL template. %% Currently, this is done by checking if its reported source path ends with %% ".dtl.erl". getFileType(Module) when is_atom(Module) -> @@ -135,7 +135,7 @@ getFileType(Source) when is_list(Source) -> ".ex" -> elixir end. -%% @private This will search back to find an appropriate include directory, by +%% This will search back to find an appropriate include directory, by %% searching further back than "..". Instead, it will extract the basename %% (probably "include" from the include pathfile, and then search backwards in %% the directory tree until it finds a directory with the same basename found @@ -146,17 +146,16 @@ determineIncludeDir(IncludeDir, BeamDir, SrcDir) -> {ok, D} -> {ok, D}; undefined -> {ok, Cwd} = file:get_cwd(), - Cwd2 = normalizeCaseWindowsDir(Cwd), - SrcDir2 = normalizeCaseWindowsDir(SrcDir), - IncludeBase2 = normalizeCaseWindowsDir(IncludeBase), - case findIncludeDirFromAncestors(Cwd2, IncludeBase2, SrcDir2) of + % Cwd2 = normalizeCaseWindowsDir(Cwd), + % SrcDir2 = normalizeCaseWindowsDir(SrcDir), + % IncludeBase2 = normalizeCaseWindowsDir(IncludeBase), + case findIncludeDirFromAncestors(Cwd, IncludeBase, SrcDir) of {ok, D} -> {ok, D}; undefined -> {ok, IncludeDir} %% Failed, just stick with original end end. -%% @private First try to see if we have an include file alongside our ebin -%directory, which is typically the case +%% First try to see if we have an include file alongside our ebin directory, which is typically the case determineIncludeDirFromBeamDir(IncludeBase, BeamDir) -> BeamBasedIncDir = filename:join(filename:dirname(BeamDir), IncludeBase), case filelib:is_dir(BeamBasedIncDir) of @@ -164,8 +163,7 @@ determineIncludeDirFromBeamDir(IncludeBase, BeamDir) -> false -> undefined end. -%% @private Then we dig back through the parent directories until we find our -%include directory +%% Then we dig back through the parent directories until we find our include directory findIncludeDirFromAncestors(Cwd, _, Cwd) -> undefined; findIncludeDirFromAncestors(_, _, "/") -> undefined; findIncludeDirFromAncestors(_, _, ".") -> undefined; @@ -179,26 +177,26 @@ findIncludeDirFromAncestors(Cwd, IncludeBase, Dir) -> findIncludeDirFromAncestors(Cwd, IncludeBase, filename:dirname(Dir)) end. -normalizeCaseWindowsDir(Dir) -> - case os:type() of - {win32, _} -> string:to_lower(Dir); - {unix, _} -> Dir - end. +% normalizeCaseWindowsDir(Dir) -> +% case os:type() of +% {win32, _} -> Dir; %string:to_lower(Dir); +% {unix, _} -> Dir +% end. -%% @private This is an attempt to intelligently fix paths in modules when a +%% This is an attempt to intelligently fix paths in modules when a %% release is moved. Essentially, it takes a module name and its original path %% from Module:module_info(compile), say %% "/some/original/path/site/src/pages/somepage.erl", and then breaks down the %% path one by one prefixing it with the current working directory until it %% either finds a match, or fails. If it succeeds, it returns the Path to the %% new Source file. -fixDescendantSource([], _IsFile) -> +fixDescendantSource([]) -> undefined; -fixDescendantSource(Path, IsFile) -> - PathParts = filename:split(Path), +fixDescendantSource(Path) -> {ok, Cwd} = file:get_cwd(), + PathParts = filename:split(Path), case makeDescendantSource(Cwd, PathParts) of - undefined -> useOriginalIfExists(Path, IsFile); + undefined -> Path; FoundPath -> FoundPath end. @@ -211,14 +209,6 @@ makeDescendantSource(Cwd, [_ | T]) -> false -> makeDescendantSource(Cwd, T) end. -useOriginalIfExists(Path, IsFile) -> - case IsFile of - true -> Path; - false -> undefined - end. - -%% @private returns true if the provided path is a descendant of the current -%% working directory. isDescendent(Path) -> {ok, Cwd} = file:get_cwd(), lists:sublist(Path, length(Cwd)) == Cwd. @@ -240,11 +230,10 @@ getSrcDir(Dir, Ctr) -> true -> getSrcDir(filename:dirname(Dir), Ctr - 1) end. -%% @private Return all files in a directory matching a regex. +%% Return all files in a directory matching a regex. wildcard(Dir, Regex) -> filelib:fold_files(Dir, Regex, true, fun(Y, Acc) -> [Y | Acc] end, []). -%% @private Get an environment variable. getEnv(Var, Default) -> case application:get_env(erlSync, Var) of {ok, Value} -> @@ -253,26 +242,19 @@ getEnv(Var, Default) -> Default end. -%% @private Set a erlSync environment variable. setEnv(Var, Val) -> ok = application:set_env(erlSync, Var, Val). -log_success(Message) -> - can_we_log(success) - andalso error_logger:info_msg(lists:flatten(Message)). - -log_errors(Message) -> - can_we_log(errors) - andalso error_logger:error_msg(lists:flatten(Message)). +logSuccess(Message) -> + canLog(success) andalso error_logger:info_msg(lists:flatten(Message)). -log_warnings(Message) -> - can_we_log(warnings) - andalso error_logger:warning_msg(lists:flatten(Message)). +logErrors(Message) -> + canLog(errors) andalso error_logger:error_msg(lists:flatten(Message)). -can_we_log(MsgType) -> - can_we_notify(log, MsgType). +logWarnings(Message) -> + canLog(warnings) andalso error_logger:warning_msg(lists:flatten(Message)). -can_we_notify(_GrowlOrLog, MsgType) -> +canLog(MsgType) -> case esScanner:getLog() of true -> true; all -> true; @@ -283,61 +265,24 @@ can_we_notify(_GrowlOrLog, MsgType) -> _ -> false end. -%% @private Return a list of all modules that belong to Erlang rather -%% than whatever application we may be running. +%% 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 + 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 ], - F = + FAppMod = fun(App) -> case application:get_key(App, modules) of {ok, Modules} -> Modules; _Other -> [] end end, - lists:flatten([F(X) || X <- Apps]). + lists:flatten([FAppMod(X) || X <- Apps]). %% 注意 map类型的数据不能当做key -type key() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple().