diff --git a/README.md b/README.md index ac13394..131e176 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,50 @@ -# erlSync - otp21.2+ +# eSync Erlang即时重新编译和重新加载! + ## 基于 [fsnotify](https://github.com/fsnotify/fsnotify) 跨平台文件系统通知。 + ## 改造自 [sync](https://github.com/rustyio/sync) -## 封装的监听文件项目[fileSync](https://github.com/SisMaker/fileSync) 如果要自己构建执行文件, 拉取监听文件项目, 然后 go build 复制执行文件到该工程的 priv 目录即可 + # 特点 + 本项目实现了自带编译与加载功能,另外支持额外的编译命令,但是执行额外的编译命令是通过os:cmd(),会阻塞VM不是很建议使用. - 启动后,erlSync会收集监听目录下的源文件和编译选项等的信息。 + 启动后,eSync会收集监听目录下的源文件和编译选项等的信息。 不仅适用于开发模式,也可以在生产环境中运行。 注意:linux下拉取项目后 需要给priv目录下的执行文件添加执行权限 - + # 使用 + 启动自动编译与加载 - erlSync:run(). + eSync:run(). 暂停自动编译与加载 - erlSync:pause(). + eSync:pause(). + 停止自动编译应用 + eSync:stop(). 启动或者关闭集群同步加载 - erlSync:swSyncNode(TrueOrFalse). + eSync:swSyncNode(TrueOrFalse). 设置编译与加载日志提示 - erlSync:setLog(Val). + eSync:setLog(Val). 设置加载后的钩子函数(支持匿名函数, {Mod, Fun}(Fun函数只有一个参数)格式, 以及他们的列表组合) - erlSync:setOnsync(FunOrFuns). - + eSync:setOnMSync(FunOrFuns). + eSync:setOnCSync(FunOrFuns). + # 配置说明 - 参见erlSync.sample.config + + 参见eSync.sample.config 默认配置为 [ - {erlSync, + {eSync, [ - {listenPort, 12369}, {compileCmd, undefined}, {extraDirs, undefined} {log, all}, - {descendant, fix} + {descendant, fix}, + {onMSyncFun, undefined}, + {onCSyncFun, undefined}, + {swSyncNode, false}, + {isJustMem, false}, + {debugInfoKeyFun, undefined} ] } ] \ No newline at end of file diff --git a/erlSync.sample.config b/erlSync.sample.config index 2ade188..34580b9 100644 --- a/erlSync.sample.config +++ b/erlSync.sample.config @@ -1,8 +1,5 @@ [ - {erlSync, [ - %% 接受fileSync的监听端口 - {listenPort, 12369}, - + {eSync, [ %% 编译命令 支持项目自带的编译命令 也可以用该项目自带的编译逻辑 %% "rebar3 compile" | "start compile.bat" | "make" {compileCmd, undefined}, @@ -13,15 +10,16 @@ {log, all}, %% 这个参数用于设置特殊目录下的文件检查编译与加载 - %% 格式:{extraDirs, [{strategy(), [srcDirDescr()]}} | {srcDirs, undefined]} - %% -type strategy() :: add | only | del. - %% 如果 strategy() when add, 会无条件监听添加的指定目录及其子目录同步编译与加载. + %% 格式:{extraDirs, [{strategy(), [srcDirDescr()]}] | undefined} + %% -type strategy() :: only | del | addExtra | addOnly. + %% 如果 strategy() when addExtra, 会无条件监听添加的指定目录及其子目录同步编译与加载. + %% 如果 strategy() when addOnly, 会无条件监听添加的指定目录(不含子目录)同步编译与加载. %% 如果 strategy() when only, 仅仅监听指定目录及其子目录下的文件编译与加载. %% 如果 strategy() when del, 则不会监听该目录及其子目录下的文件. %% -type srcDirDescr() :: { Dir :: file:filename(), [Options :: compile_option()]}. %% 默认值:undefined 根据当前工作目录 和 已经加载的模块做来得出需要扫描的目录 - %%示例: {extraDirs, [{add, [{"./_build/default/lib/erlGbh", []}]}, {only, [{"./", []}]}, {del, [{"./_build", []}]}]}. - %%{extraDirs, [{add, [{"./_build/default/lib/erlGbh", []}, {"./_build/default/lib/erlSync/ebin", []}]}, {only, [{"./", []}]}, {del, [{"./_build", []}]}]} + %%示例: {extraDirs, [{addExtra, [{"./_build/default/lib/erlGbh", []}]}, {only, [{"./", []}]}, {del, [{"./_build", []}]}]}. + %%{extraDirs, [{add, [{"./_build/default/lib/erlGbh", []}, {"./_build/default/lib/eSync/ebin", []}]}, {only, [{"./", []}]}, {del, [{"./_build", []}]}]} {extraDirs, [{strategy(), [srcDirDescr()]}]}, %% 这个参数用来设置 怎么处理 当beam文件的源文件目录不是当前工作的子目录时的情况 @@ -30,8 +28,22 @@ %% * allow = 不要做任何特别的事情,使用beam源文件原始路径查找该文件 %% * ignore = 而忽略对其源路径的任何更改 %% 默认值: fix - {descendant, fix} - ]} -]. + {descendant, fix}, + + %% Beam更新回调函数 格式: undefined | {Mondule, Fun} | [{Mondule, Fun}, ...], Fun函数只有一个参数 + {onMSyncFun, undefined}, + %% config更新回调函数 格式: undefined | {Mondule, Fun} | [{Mondule, Fun}, ...], Fun函数只有一个参数 + {onCSyncFun, undefined}, + %% 是否开启集群同步加载 + {swSyncNode, false}, + + %% 仅仅内存编译还是编译写入到磁盘去 + {isJustMem, false}, + + %% 如果存在debug_info_key 需要用户提供获取debug_info_key的函数 格式: undefined | {Mondule, Fun} + %% this fun need return: {debug_info_key, xxx} + {debugInfoKeyFun, undefined} + ]} +]. \ No newline at end of file diff --git a/include/erlSync.hrl b/include/erlSync.hrl deleted file mode 100644 index 94553eb..0000000 --- a/include/erlSync.hrl +++ /dev/null @@ -1,23 +0,0 @@ --define(LOG_ON(Val), Val == true; Val == all; Val == skip_success; is_list(Val), Val =/= []). - --define(TCP_DEFAULT_OPTIONS, [ - binary - , {packet, 4} - , {active, true} - , {reuseaddr, true} - , {nodelay, false} - , {delay_send, true} - , {send_timeout, 15000} - , {keepalive, true} - , {exit_on_close, true}]). - --define(Log, log). --define(listenPort, listenPort). --define(compileCmd, compileCmd). --define(extraDirs, extraDirs). --define(descendant, descendant). --define(CfgList, [{?Log, all}, {?listenPort, 12369}, {?compileCmd, undefined}, {?extraDirs, undefined}, {?descendant, fix}]). - --define(esCfgSync, esCfgSync). --define(rootSrcDir, <<"src">>). --define(esRecompileCnt, '$esRecompileCnt'). \ No newline at end of file diff --git a/priv/fileSync b/priv/fileSync index 0797917..567f3e3 100644 Binary files a/priv/fileSync and b/priv/fileSync differ diff --git a/priv/fileSync.exe b/priv/fileSync.exe index 173b584..89694c3 100644 Binary files a/priv/fileSync.exe and b/priv/fileSync.exe differ diff --git a/src/erlSync.app.src b/src/eSync.app.src similarity index 71% rename from src/erlSync.app.src rename to src/eSync.app.src index b10828e..34ad414 100644 --- a/src/erlSync.app.src +++ b/src/eSync.app.src @@ -1,11 +1,11 @@ -{application, erlSync, +{application, eSync, [{description, "erlang code auto compile and loader"}, {vsn, "0.1.0"}, {registered, []}, - {mod, {erlSync_app, []}}, + {mod, {eSync, []}}, {applications, [kernel, stdlib, syntax_tools, compiler]}, {env, []}, {modules, []}, - {licenses, ["MIT License"]}, + {licenses, ["MIT"]}, {links, []} ]}. diff --git a/src/eSync.erl b/src/eSync.erl new file mode 100644 index 0000000..6db2086 --- /dev/null +++ b/src/eSync.erl @@ -0,0 +1,1297 @@ +-module(eSync). + +-behaviour(gen_server). + +-compile(inline). +-compile({inline_size, 128}). + +-define(IIF(Cond, Ret1, Ret2), (case Cond of true -> Ret1; _ -> Ret2 end)). +-define(LOG_ON(Val), Val == true; Val == all; Val == skip_success; is_list(Val), Val =/= []). + +-define(Log, log). +-define(compileCmd, compileCmd). +-define(extraDirs, extraDirs). +-define(descendant, descendant). +-define(onMSyncFun, onMSyncFun). +-define(onCSyncFun, onCSyncFun). +-define(swSyncNode, swSyncNode). +-define(isJustMem, isJustMem). +-define(debugInfoKeyFun, debugInfoKeyFun). + +-define(DefCfgList, [{?Log, all}, {?compileCmd, undefined}, {?extraDirs, undefined}, {?descendant, fix}, {?onMSyncFun, undefined}, {?onCSyncFun, undefined}, {?swSyncNode, false}, {?isJustMem, false}, {?debugInfoKeyFun, undefined}]). + +-define(esCfgSync, esCfgSync). +-define(rootSrcDir, <<"src">>). + +-export([ + start/2, + start/0, + stop/0, + run/0 +]). + +%% API +-export([ + start_link/0, + rescan/0, + pause/0, + unpause/0, + setLog/1, + getLog/0, + curInfo/0, + getOnMSync/0, + setOnMSync/1, + getOnCSync/0, + setOnCSync/1, + swSyncNode/1 +]). + +%% gen_server callbacks +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2 +]). + +start(_StartType, _StartArgs) -> + start_link(). + +start() -> + application:ensure_all_started(eSync). + +stop() -> + application:stop(eSync). + +run() -> + case start() of + {ok, _Started} -> + unpause(), + ok; + {error, Reason} -> + logErrors("start eSync error:~p~n", [Reason]) + end. + +-define(SERVER, ?MODULE). +-define(None, 0). + +-record(state, { + status = wait + , port = undefined + , onMSyncFun = undefined + , onCSyncFun = undefined + , swSyncNode = false + , srcFiles = #{} :: map() + , hrlFiles = #{} :: map() + , configs = #{} :: map() + , beams = #{} :: map() +}). + +%% ************************************ API start *************************** +rescan() -> + es_gen_ipc:cast(?SERVER, miRescan), + logSuccess("start rescaning source files..."), + ok. + +unpause() -> + es_gen_ipc:cast(?SERVER, miUnpause), + ok. + +pause() -> + es_gen_ipc:cast(?SERVER, miPause), + logSuccess("Pausing eSync. Call eSync:run() to restart"), + ok. + +curInfo() -> + es_gen_ipc:call(?SERVER, miCurInfo). + +setLog(T) when ?LOG_ON(T) -> + setEnv(log, T), + loadCfg(), + logSuccess("Console Notifications Enabled"), + ok; +setLog(_) -> + setEnv(log, none), + loadCfg(), + logSuccess("Console Notifications Disabled"), + ok. + +getLog() -> + ?esCfgSync:getv(log). + +swSyncNode(IsSync) -> + es_gen_ipc:cast(?SERVER, {miSyncNode, IsSync}), + ok. + +getOnMSync() -> + es_gen_ipc:call(?SERVER, miGetOnMSync). + +setOnMSync(Fun) -> + es_gen_ipc:call(?SERVER, {miSetOnMSync, Fun}). + +getOnCSync() -> + es_gen_ipc:call(?SERVER, miGetOnCSync). + +setOnCSync(Fun) -> + es_gen_ipc:call(?SERVER, {miSetOnCSync, Fun}). + +%% ************************************ API end *************************** + +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +init([]) -> + erlang:process_flag(trap_exit, true), + loadCfg(), + erlang:send_after(0, self(), doAfter), + {ok, #state{onMSyncFun = ?esCfgSync:getv(?onMSyncFun), onCSyncFun = ?esCfgSync:getv(?onCSyncFun), swSyncNode = ?esCfgSync:getv(?swSyncNode)}}. + +handle_call(miGetOnMSync, _, #state{onMSyncFun = OnMSyncFun} = State) -> + {reply, OnMSyncFun, State}; +handle_call({miSetOnMSync, Fun}, _, State) -> + {reply, ok, State#state{onMSyncFun = Fun}}; +handle_call(miGetOnCSync, _, #state{onCSyncFun = OnCSyncFun} = State) -> + {reply, OnCSyncFun, State}; +handle_call({miSetOnCSync, Fun}, _, State) -> + {reply, ok, State#state{onCSyncFun = Fun}}; +handle_call(miCurInfo, _, State) -> + {reply, {erlang:get(), State}, State}; +handle_call(_Request, _, State) -> + {reply, ok, State}. + +handle_cast(miPause, State) -> + {noreply, State#state{status = pause}}; +handle_cast(miUnpause, State) -> + {noreply, State#state{status = running}}; +handle_cast({miSyncNode, IsSync}, State) -> + case IsSync of + true -> + {noreply, State#state{swSyncNode = true}}; + _ -> + {noreply, State#state{swSyncNode = false}} + end; +handle_cast(miRescan, State) -> + {Srcs, Hrls, Configs, Beams} = collSrcFiles(false), + {noreply, State#state{srcFiles = Srcs, hrlFiles = Hrls, configs = Configs, beams = Beams}, hibernate}; +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({Port, {data, Data}}, #state{status = Status, srcFiles = Srcs, hrlFiles = Hrls, configs = Configs, beams = Beams, onMSyncFun = OnMSyncFun, onCSyncFun = OnCSyncFun, swSyncNode = SwSyncNode} = State) -> + case Status of + running -> + FileList = binary:split(Data, <<"\r\n">>, [global]), + %% 收集改动了beam hrl src 文件 然后执行相应的逻辑 + {CBeams, CConfigs, CHrls, CSrcs, NewSrcs, NewHrls, NewConfigs, NewBeams} = classifyChangeFile(FileList, [], [], #{}, #{}, Srcs, Hrls, Configs, Beams), + fireOnSync(OnCSyncFun, CConfigs), + reloadChangedMod(CBeams, SwSyncNode, OnMSyncFun, []), + case ?esCfgSync:getv(?compileCmd) of + undefined -> + LastCHrls = collIncludeCHrls(maps:keys(CHrls), NewHrls, CHrls, #{}), + NReSrcs = collIncludeCErls(maps:keys(LastCHrls), NewSrcs, CSrcs, #{}), + recompileChangeSrcFile(maps:iterator(NReSrcs), SwSyncNode), + {noreply, State#state{srcFiles = NewSrcs, hrlFiles = NewHrls, configs = NewConfigs, beams = NewBeams}}; + CmdStr -> + case maps:size(CSrcs) > 0 orelse CHrls =/= [] of + true -> + RetStr = os:cmd(CmdStr), + RetList = string:split(RetStr, "\n", all), + logSuccess("compile cmd:~p ~n", [CmdStr]), + logSuccess("the result: ~n ", []), + [ + begin + logSuccess("~p ~n", [OneRet]) + end || OneRet <- RetList, OneRet =/= [] + ], + ok; + _ -> + ignore + end, + {noreply, State} + end; + pause -> + {noreply, State}; + wait -> + case Data of + <<"init">> -> + %% port启动成功 先发送监听目录配置 + {AddExtraSrcDirs, AddOnlySrcDirs, OnlySrcDirs, DelSrcDirs} = mergeExtraDirs(false), + AddExtraStr = string:join([filename:nativename(OneDir) || OneDir <- AddExtraSrcDirs], "|"), + AddOnlyStr = string:join([filename:nativename(OneDir) || OneDir <- AddOnlySrcDirs], "|"), + OnlyStr = string:join([filename:nativename(OneDir) || OneDir <- OnlySrcDirs], "|"), + DelStr = string:join([filename:nativename(OneDir) || OneDir <- DelSrcDirs], "|"), + AllStr = string:join([AddExtraStr, AddOnlyStr, OnlyStr, DelStr], "\r\n"), + erlang:port_command(Port, AllStr), + logSuccess("eSync connect fileSync success..."), + %% 然后收集一下监听目录下的src文件 + {BSrcs, BHrls, BConfigs, BBeams} = collSrcFiles(true), + {noreply, State#state{status = running, srcFiles = BSrcs, hrlFiles = BHrls, configs = BConfigs, beams = BBeams}, hibernate}; + _ -> + logErrors("error, eSync receive unexpect port msg ~p~n", [Data]), + {noreply, State} + end + end; +handle_info({Port, closed}, #state{port = Port} = _State) -> + logErrors("eSync receive port closed ~n"), + {stop, port_close, _State}; +handle_info({'EXIT', Port, Reason}, #state{port = Port} = _State) -> + logErrors("eSync receive port exit Reason:~p ~n", [Reason]), + {stop, {port_EXIT, Reason}, _State}; +handle_info({Port, {exit_status, Status}}, #state{port = Port} = _State) -> + logErrors("eSync receive port exit_status Status:~p ~p ~n", [Status, Port]), + {stop, {port_exit_status, Status}, _State}; +handle_info({'EXIT', _Pid, _Reason}, _State) -> + {noreply, _State}; +handle_info(doAfter, State) -> + %% 启动port 发送监听目录信息 + PortName = fileSyncPath("fileSync"), + Opts = [{packet, 4}, binary, exit_status, use_stdio], + Port = erlang:open_port({spawn_executable, PortName}, Opts), + {noreply, State#state{port = Port}, 4000}; +handle_info(timeout, State) -> + logErrors("failed to connect the fileSync to stop state:~p ~n", [State]), + {stop, waitConnOver, State}; +handle_info(_Msg, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +%% ************************************************* utils start ******************************************************* +getModSrcDir(Module) -> + case code:is_loaded(Module) of + {file, _} -> + try + %% Get some module info... + Props = Module:module_info(compile), + Source = proplists:get_value(source, Props, ""), + %% Ensure that the file exists, is a decendent of the tree, and how to deal with that + IsFile = filelib:is_regular(Source), + IsDescendant = isDescendent(Source), + Descendant = ?esCfgSync:getv(?descendant), + LastSource = + case {IsFile, IsDescendant, Descendant} of + %% is file and descendant, we're good to go + {true, true, _} -> Source; + %% is not a descendant, but we allow them, so good to go + {true, false, allow} -> Source; + %% is not a descendant, and we fix non-descendants, so let's fix it + {_, false, fix} -> fixDescendantSource(Source, IsFile); + %% Anything else, and we don't know what to do, so let's just bail. + _ -> undefined + end, + case LastSource of + undefined -> + undefined; + _ -> + %% Get the source dir... + Dir = filename:dirname(LastSource), + getSrcDir(Dir) + end + catch _ : _ -> + undefined + end; + _ -> + undefined + end. + +getModOpts(Module) -> + case code:is_loaded(Module) of + {file, _} -> + try + Props = Module:module_info(compile), + BeamDir = filename:dirname(code:which(Module)), + Options1 = proplists:get_value(options, Props, []), + %% transform `outdir' + Options2 = transformOutdir(BeamDir, Options1), + Options3 = ensureInclude(Options2), + %% transform the include directories + Options4 = transformAllIncludes(Module, BeamDir, Options3), + %% maybe_add_compile_info + Options5 = maybeAddCompileInfo(Options4), + %% add filetype to options (DTL, LFE, erl, etc) + Options6 = addFileType(Module, Options5), + Options7 = lists:keyreplace(debug_info_key, 1, Options6, debugInfoKeyFun()), + {ok, Options7} + catch ExType:Error -> + logWarnings("~p:0: ~p looking for options: ~p. ~n", [Module, ExType, Error]), + undefined + end; + _ -> + undefined + end. + +tryGetModOpts(Module) -> + try + Props = Module:module_info(compile), + BeamDir = filename:dirname(code:which(Module)), + Options1 = proplists:get_value(options, Props, []), + %% transform `outdir' + Options2 = transformOutdir(BeamDir, Options1), + Options3 = ensureInclude(Options2), + %% transform the include directories + Options4 = transformAllIncludes(Module, BeamDir, Options3), + %% maybe_add_compile_info + Options5 = maybeAddCompileInfo(Options4), + %% add filetype to options (DTL, LFE, erl, etc) + Options6 = addFileType(Module, Options5), + Options7 = lists:keyreplace(debug_info_key, 1, Options6, debugInfoKeyFun()), + {ok, Options7} + catch _ExType:_Error -> + undefiend + end. + +tryGetSrcOpts(SrcDir) -> + %% Then we dig back through the parent directories until we find our include directory + NewDirName = filename:dirname(SrcDir), + case getOptions(NewDirName) of + {ok, _Options} = Opts -> + Opts; + _ -> + BaseName = filename:basename(SrcDir), + IsBaseSrcDir = BaseName == ?rootSrcDir, + case NewDirName =/= SrcDir andalso not IsBaseSrcDir of + true -> + tryGetSrcOpts(NewDirName); + _ when IsBaseSrcDir -> + try filelib:fold_files(SrcDir, ".*\\.(erl|dtl|lfe|ex)$", true, + fun(OneFile, Acc) -> + Mod = binary_to_atom(filename:basename(OneFile, filename:extension(OneFile))), + case tryGetModOpts(Mod) of + {ok, _Options} = Opts -> + throw(Opts); + _ -> + Acc + end + end, undefined) + catch + {ok, _Options} = Opts -> + Opts; + _ExType:_Error -> + logWarnings("looking src options error ~p:~p. ~n", [_ExType, _Error]), + undefined + end; + _ -> + undefined + end + end. + +transformOutdir(BeamDir, Options) -> + [{outdir, BeamDir} | proplists:delete(outdir, Options)]. + +ensureInclude(Options) -> + case proplists:get_value(i, Options) of + undefined -> [{i, "include"} | Options]; + _ -> Options + end. + +transformAllIncludes(Module, BeamDir, Options) -> + [begin + case Opt of + {i, IncludeDir} -> + {ok, SrcDir} = getModSrcDir(Module), + {ok, IncludeDir2} = determineIncludeDir(IncludeDir, BeamDir, SrcDir), + {i, IncludeDir2}; + _ -> + Opt + end + end || Opt <- Options]. + +maybeAddCompileInfo(Options) -> + case lists:member(compile_info, Options) of + true -> Options; + false -> addCompileInfo(Options) + end. + +addCompileInfo(Options) -> + CompInfo = [{K, V} || {K, V} <- Options, lists:member(K, [outdir, i])], + [{compile_info, CompInfo} | Options]. + +addFileType(Module, Options) -> + Type = getFileType(Module), + [{type, Type} | Options]. + +%% This will check if the given module or source file is an ErlyDTL template. +%% Currently, this is done by checking if its reported source path ends with +%% ".dtl.erl". +getFileType(Module) when is_atom(Module) -> + Props = Module:module_info(compile), + Source = proplists:get_value(source, Props, ""), + getFileType(Source); + +getFileType(Source) -> + Ext = filename:extension(Source), + Root = filename:rootname(Source), + SecondExt = filename:extension(Root), + case Ext of + <<".erl">> when SecondExt =:= <<".dtl">> -> dtl; + <<".dtl">> -> dtl; + <<".erl">> -> erl; + <<".lfe">> -> lfe; + <<".ex">> -> elixir; + ".erl" when SecondExt =:= ".dtl" -> dtl; + ".dtl" -> dtl; + ".erl" -> erl; + ".lfe" -> lfe; + ".ex" -> elixir + end. + +%% This will search back to find an appropriate include directory, by +%% searching further back than "..". Instead, it will extract the basename +%% (probably "include" from the include pathfile, and then search backwards in +%% the directory tree until it finds a directory with the same basename found +%% above. +determineIncludeDir(IncludeDir, BeamDir, SrcDir) -> + IncludeBase = filename:basename(IncludeDir), + case determineIncludeDirFromBeamDir(IncludeBase, IncludeDir, BeamDir) of + {ok, _Dir} = RetD -> RetD; + undefined -> + {ok, Cwd} = file:get_cwd(), + % Cwd2 = normalizeCaseWindowsDir(Cwd), + % SrcDir2 = normalizeCaseWindowsDir(SrcDir), + % IncludeBase2 = normalizeCaseWindowsDir(IncludeBase), + case findIncludeDirFromAncestors(SrcDir, Cwd, IncludeBase) of + {ok, _Dir} = RetD -> RetD; + undefined -> {ok, IncludeDir} %% Failed, just stick with original + end + end. + +%% First try to see if we have an include file alongside our ebin directory, which is typically the case +determineIncludeDirFromBeamDir(IncludeBase, IncludeDir, BeamDir) -> + BeamBasedIncDir = filename:join(filename:dirname(BeamDir), IncludeBase), + case filelib:is_dir(BeamBasedIncDir) of + true -> {ok, BeamBasedIncDir}; + false -> + BeamBasedIncDir2 = filename:join(filename:dirname(BeamDir), IncludeDir), + case filelib:is_dir(BeamBasedIncDir2) of + true -> {ok, BeamBasedIncDir2}; + _ -> + undefined + end + end. + +%% get the src dir +getRootSrcDirFromSrcDir(SrcDir) -> + NewDirName = filename:dirname(SrcDir), + BaseName = filename:basename(SrcDir), + case BaseName of + ?rootSrcDir -> + NewDirName; + _ -> + case NewDirName =/= SrcDir of + true -> + getRootSrcDirFromSrcDir(NewDirName); + _ -> + undefined + end + end. + +%% Then we dig back through the parent directories until we find our include directory +findIncludeDirFromAncestors(Cwd, Cwd, _) -> undefined; +findIncludeDirFromAncestors("/", _, _) -> undefined; +findIncludeDirFromAncestors(".", _, _) -> undefined; +findIncludeDirFromAncestors("", _, _) -> undefined; +findIncludeDirFromAncestors(Dir, Cwd, IncludeBase) -> + NewDirName = filename:dirname(Dir), + AttemptDir = filename:join(NewDirName, IncludeBase), + case filelib:is_dir(AttemptDir) of + true -> + {ok, AttemptDir}; + false -> + case NewDirName =/= Dir of + true -> + findIncludeDirFromAncestors(NewDirName, Cwd, IncludeBase); + _ -> + undefined + end + end. + +% normalizeCaseWindowsDir(Dir) -> +% case os:type() of +% {win32, _} -> Dir; %string:to_lower(Dir); +% {unix, _} -> Dir +% end. + +%% This is an attempt to intelligently fix paths in modules when a +%% release is moved. Essentially, it takes a module name and its original path +%% from Module:module_info(compile), say +%% "/some/original/path/site/src/pages/somepage.erl", and then breaks down the +%% path one by one prefixing it with the current working directory until it +%% either finds a match, or fails. If it succeeds, it returns the Path to the +%% new Source file. +fixDescendantSource([], _IsFile) -> + undefined; +fixDescendantSource(Path, IsFile) -> + {ok, Cwd} = file:get_cwd(), + PathParts = filename:split(Path), + case makeDescendantSource(PathParts, Cwd) of + undefined -> case IsFile of true -> Path; _ -> undefined end; + FoundPath -> FoundPath + end. + +makeDescendantSource([], _Cwd) -> + undefined; +makeDescendantSource([_ | T], Cwd) -> + PathAttempt = filename:join([Cwd | T]), + case filelib:is_regular(PathAttempt) of + true -> PathAttempt; + false -> makeDescendantSource(T, Cwd) + end. + +isDescendent(Path) -> + {ok, Cwd} = file:get_cwd(), + lists:sublist(Path, length(Cwd)) == Cwd. + +%% @private Find the src directory for the specified Directory; max 15 iterations +getSrcDir(Dir) -> + getSrcDir(Dir, 15). + +getSrcDir(_Dir, 0) -> + undefined; +getSrcDir(Dir, Ctr) -> + HasCode = filelib:wildcard("*.erl", Dir) /= [] orelse + filelib:wildcard("*.hrl", Dir) /= [] orelse + filelib:wildcard("*.dtl", Dir) /= [] orelse + filelib:wildcard("*.lfe", Dir) /= [] orelse + filelib:wildcard("*.ex", Dir) /= [], + if + HasCode -> {ok, Dir}; + true -> getSrcDir(filename:dirname(Dir), Ctr - 1) + end. + +mergeExtraDirs(IsAddPath) -> + case ?esCfgSync:getv(?extraDirs) of + undefined -> + {[], [], [], []}; + ExtraList -> + FunMerge = + fun(OneExtra, {AddExtraDirs, AddOnlyDirs, OnlyDirs, DelDirs} = AllAcc) -> + case OneExtra of + {addExtra, DirsAndOpts} -> + Adds = + [ + begin + case IsAddPath of + true -> + case proplists:get_value(outdir, Opts) of + undefined -> + true; + Path -> + ok = filelib:ensure_dir(Path), + true = code:add_pathz(Path) + end; + _ -> + ignore + end, + filename:absname(Dir) + end || {Dir, Opts} <- DirsAndOpts + ], + setelement(1, AllAcc, Adds ++ AddExtraDirs); + {addOnly, DirsAndOpts} -> + Adds = + [ + begin + case IsAddPath of + true -> + case proplists:get_value(outdir, Opts) of + undefined -> + true; + Path -> + ok = filelib:ensure_dir(Path), + true = code:add_pathz(Path) + end; + _ -> + ignore + end, + filename:absname(Dir) + end || {Dir, Opts} <- DirsAndOpts + ], + setelement(2, AllAcc, Adds ++ AddOnlyDirs); + {only, DirsAndOpts} -> + Onlys = + [ + begin + case IsAddPath of + true -> + case proplists:get_value(outdir, Opts) of + undefined -> + true; + Path -> + ok = filelib:ensure_dir(Path), + true = code:add_pathz(Path) + end; + _ -> + ignore + end, + filename:absname(Dir) + end || {Dir, Opts} <- DirsAndOpts + ], + setelement(3, AllAcc, Onlys ++ OnlyDirs); + {del, DirsAndOpts} -> + Dels = + [ + begin + filename:absname(Dir) + end || {Dir, _Opts} <- DirsAndOpts + ], + setelement(4, AllAcc, Dels ++ DelDirs) + end + end, + lists:foldl(FunMerge, {[], [], [], []}, ExtraList) + end. + +-define(RegExp, <<".*\\.(erl|hrl|beam|config|dtl|lfe|ex)$">>). +collSrcFiles(IsAddPath) -> + {AddExtraSrcDirs, AddOnlySrcDirs, OnlySrcDirs, DelSrcDirs} = mergeExtraDirs(IsAddPath), + CollFiles = filelib:fold_files(filename:absname(<<"./">>), ?RegExp, true, + fun(OneFile, {Srcs, Hrls, Configs, Beams} = Acc) -> + case isOnlyDir(OnlySrcDirs, OneFile) andalso (not isDelDir(DelSrcDirs, OneFile)) of + true -> + MTimeSec = dateTimeToSec(filelib:last_modified(OneFile)), + case filename:extension(OneFile) of + <<".beam">> -> + BeamMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)), + setelement(4, Acc, Beams#{BeamMod => MTimeSec}); + <<".config">> -> + setelement(3, Acc, Configs#{OneFile => MTimeSec}); + <<".hrl">> -> + setelement(2, Acc, Hrls#{OneFile => MTimeSec}); + <<>> -> + Acc; + _ -> + RootSrcDir = + case getRootSrcDirFromSrcDir(OneFile) of + undefined -> + filename:dirname(OneFile); + RetSrcDir -> + RetSrcDir + end, + case getOptions(RootSrcDir) of + undefined -> + Mod = binary_to_atom(filename:basename(OneFile, filename:extension(OneFile))), + case getModOpts(Mod) of + {ok, Options} -> + setOptions(RootSrcDir, Options); + _ -> + ignore + end; + _ -> + ignore + end, + setelement(1, Acc, Srcs#{OneFile => MTimeSec}) + end; + _ -> + Acc + end + end, {#{}, #{}, #{}, #{}}), + + FunCollAddExtra = + fun(OneDir, FilesAcc) -> + filelib:fold_files(?IIF(is_list(OneDir), list_to_binary(OneDir), OneDir), ?RegExp, true, + fun(OneFile, {Srcs, Hrls, Configs, Beams} = Acc) -> + MTimeSec = dateTimeToSec(filelib:last_modified(OneFile)), + case filename:extension(OneFile) of + <<".beam">> -> + BeamMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)), + setelement(4, Acc, Beams#{BeamMod => MTimeSec}); + <<".config">> -> + setelement(3, Acc, Configs#{OneFile => MTimeSec}); + <<".hrl">> -> + setelement(2, Acc, Hrls#{OneFile => MTimeSec}); + <<>> -> + Acc; + _ -> + setelement(1, Acc, Srcs#{OneFile => MTimeSec}) + end + end, FilesAcc) + end, + AddExtraCollFiles = lists:foldl(FunCollAddExtra, CollFiles, AddExtraSrcDirs), + + FunCollAddOnly = + fun(OneDir, FilesAcc) -> + filelib:fold_files(?IIF(is_list(OneDir), list_to_binary(OneDir), OneDir), ?RegExp, false, + fun(OneFile, {Srcs, Hrls, Configs, Beams} = Acc) -> + MTimeSec = dateTimeToSec(filelib:last_modified(OneFile)), + case filename:extension(OneFile) of + <<".beam">> -> + BeamMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)), + setelement(4, Acc, Beams#{BeamMod => MTimeSec}); + <<".config">> -> + setelement(3, Acc, Configs#{OneFile => MTimeSec}); + <<".hrl">> -> + setelement(2, Acc, Hrls#{OneFile => MTimeSec}); + <<>> -> + Acc; + _ -> + setelement(1, Acc, Srcs#{OneFile => MTimeSec}) + end + end, FilesAcc) + end, + lists:foldl(FunCollAddOnly, AddExtraCollFiles, AddOnlySrcDirs). + + +isOnlyDir([], _) -> + true; +isOnlyDir(ReplaceDirs, SrcDir) -> + isMatchDir(ReplaceDirs, SrcDir). + +isDelDir([], _) -> + false; +isDelDir(ReplaceDirs, SrcDir) -> + isMatchDir(ReplaceDirs, SrcDir). + +isMatchDir([], _SrcDir) -> + false; +isMatchDir([SrcDir | _ReplaceDirs], SrcDir) -> + true; +isMatchDir([OneDir | ReplaceDirs], SrcDir) -> + case re:run(SrcDir, OneDir) of + nomatch -> isMatchDir(ReplaceDirs, SrcDir); + _ -> true + end. + +getEnv(Var, Default) -> + case application:get_env(eSync, Var) of + {ok, Value} -> + Value; + _ -> + Default + end. + +setEnv(Var, Val) -> + ok = application:set_env(eSync, Var, Val). + +logSuccess(Format) -> + canLog(success) andalso error_logger:info_msg(Format). +logSuccess(Format, Args) -> + canLog(success) andalso error_logger:info_msg(Format, Args). + +logErrors(Format) -> + canLog(errors) andalso error_logger:error_msg(Format). +logErrors(Format, Args) -> + canLog(errors) andalso error_logger:error_msg(Format, Args). + +logWarnings(Format, Args) -> + canLog(warnings) andalso error_logger:warning_msg(Format, Args). + +canLog(MsgType) -> + case eSync:getLog() of + true -> true; + all -> true; + none -> false; + false -> false; + skip_success -> MsgType == errors orelse MsgType == warnings; + L when is_list(L) -> lists:member(MsgType, L); + _ -> false + end. + +%% 注意 map类型的数据不能当做key +-type key() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple(). +-type value() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple() | map(). + +-spec load(term(), [{key(), value()}]) -> ok. +load(Module, KVs) -> + Forms = forms(Module, KVs), + {ok, Module, Bin} = compile:forms(Forms), + code:soft_purge(Module), + {module, Module} = code:load_binary(Module, atom_to_list(Module), Bin), + ok. + +forms(Module, KVs) -> + %% -module(Module). + Mod = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]), + %% -export([getv/0]). + ExportList = [erl_syntax:arity_qualifier(erl_syntax:atom(getv), erl_syntax:integer(1))], + Export = erl_syntax:attribute(erl_syntax:atom(export), [erl_syntax:list(ExportList)]), + %% getv(K) -> V + Function = erl_syntax:function(erl_syntax:atom(getv), lookup_clauses(KVs, [])), + [erl_syntax:revert(X) || X <- [Mod, Export, Function]]. + +lookup_clause(Key, Value) -> + Var = erl_syntax:abstract(Key), + Body = erl_syntax:abstract(Value), + erl_syntax:clause([Var], [], [Body]). + +lookup_clause_anon() -> + Var = erl_syntax:variable("_"), + Body = erl_syntax:atom(undefined), + erl_syntax:clause([Var], [], [Body]). + +lookup_clauses([], Acc) -> + lists:reverse(lists:flatten([lookup_clause_anon() | Acc])); +lookup_clauses([{Key, Value} | T], Acc) -> + lookup_clauses(T, [lookup_clause(Key, Value) | Acc]). + +getOptions(SrcDir) -> + case erlang:get(SrcDir) of + undefined -> + undefined; + Options -> + {ok, Options} + end. + +setOptions(SrcDir, Options) -> + case erlang:get(SrcDir) of + undefined -> + erlang:put(SrcDir, Options); + OldOptions -> + NewOptions = + case lists:keytake(compile_info, 1, Options) of + {value, {compile_info, ValList1}, Options1} -> + case lists:keytake(compile_info, 1, OldOptions) of + {value, {compile_info, ValList2}, Options2} -> + [{compile_info, lists:usort(ValList1 ++ ValList2)} | lists:usort(Options1 ++ Options2)]; + _ -> + lists:usort(Options ++ OldOptions) + end; + _ -> + lists:usort(Options ++ OldOptions) + end, + erlang:put(SrcDir, NewOptions) + end. + +loadCfg() -> + KVs = [{Key, getEnv(Key, DefVal)} || {Key, DefVal} <- ?DefCfgList], + load(?esCfgSync, KVs). + +%% ******************************* 加载与编译相关 ********************************************************************** +printResults(_Module, SrcFile, [], []) -> + logSuccess("~s Recompiled", [SrcFile]); +printResults(_Module, SrcFile, [], Warnings) -> + formatErrors(fun logWarnings/2, SrcFile, [], Warnings); +printResults(_Module, SrcFile, Errors, Warnings) -> + formatErrors(fun logErrors/2, SrcFile, Errors, Warnings). + + +%% @private Print error messages in a pretty and user readable way. +formatErrors(LogFun, File, Errors, Warnings) -> + AllErrors1 = lists:sort(lists:flatten([X || {_, X} <- Errors])), + AllErrors2 = [{Line, "Error", Module, Description} || {Line, Module, Description} <- AllErrors1], + AllWarnings1 = lists:sort(lists:flatten([X || {_, X} <- Warnings])), + AllWarnings2 = [{Line, "Warning", Module, Description} || {Line, Module, Description} <- AllWarnings1], + Everything = lists:sort(AllErrors2 ++ AllWarnings2), + FPck = + fun({Line, Prefix, Module, ErrorDescription}) -> + Msg = formatError(Module, ErrorDescription), + LogFun("~s: ~p: ~s: ~s", [File, Line, Prefix, Msg]) + end, + [FPck(X) || X <- Everything], + ok. + +formatError(Module, ErrorDescription) -> + case erlang:function_exported(Module, format_error, 1) of + true -> Module:format_error(ErrorDescription); + false -> io_lib:format("~s", [ErrorDescription]) + end. + +fireOnSync(OnSyncFun, Modules) -> + case OnSyncFun of + undefined -> ok; + Funs when is_list(Funs) -> onSyncApplyList(Funs, Modules); + Fun -> onSyncApply(Fun, Modules) + end. + +onSyncApplyList(Funs, Modules) -> + [onSyncApply(Fun, Modules) || Fun <- Funs]. + +onSyncApply({M, F}, Modules) -> + try erlang:apply(M, F, [Modules]) + catch + C:R:S -> + logErrors("apply sync fun ~p:~p(~p) error ~p", [M, F, Modules, {C, R, S}]) + end; +onSyncApply(Fun, Modules) when is_function(Fun) -> + try Fun(Modules) + catch + C:R:S -> + logErrors("apply sync fun ~p(~p) error ~p", [Fun, Modules, {C, R, S}]) + end. + +reloadChangedMod([], _SwSyncNode, OnSyncFun, Acc) -> + fireOnSync(OnSyncFun, Acc); +reloadChangedMod([Module | LeftMod], SwSyncNode, OnSyncFun, Acc) -> + case Module == es_gen_ipc orelse code:get_object_code(Module) of + true -> + ignore; + error -> + logErrors("Error loading object code for ~p", [Module]), + reloadChangedMod(LeftMod, SwSyncNode, OnSyncFun, Acc); + {Module, Binary, Filename} -> + case code:load_binary(Module, Filename, Binary) of + {module, Module} -> + logSuccess("Reloaded(Beam changed) Mod: ~s Success", [Module]), + syncLoadModOnAllNodes(SwSyncNode, Module, Binary, changed); + {error, What} -> + logErrors("Reloaded(Beam changed) Mod: ~s Errors Reason:~p", [Module, What]) + end, + reloadChangedMod(LeftMod, SwSyncNode, OnSyncFun, [Module | Acc]) + end. + +getNodes() -> + lists:usort(lists:flatten(nodes() ++ [erpc:call(X, erlang, nodes, []) || X <- nodes()])) -- [node()]. + +syncLoadModOnAllNodes(false, _Module, _Binary, _Reason) -> + ignore; +syncLoadModOnAllNodes(true, Module, Binary, Reason) -> + %% Get a list of nodes known by this node, plus all attached nodes. + Nodes = getNodes(), + NumNodes = length(Nodes), + [ + begin + logSuccess("Do Reloading '~s' on ~p", [Module, Node]), + erpc:call(Node, code, ensure_loaded, [Module]), + case erpc:call(Node, code, which, [Module]) of + Filename when is_binary(Filename) orelse is_list(Filename) -> + %% File exists, overwrite and load into VM. + ok = erpc:call(Node, file, write_file, [Filename, Binary]), + erpc:call(Node, code, purge, [Module]), + + case erpc:call(Node, code, load_file, [Module]) of + {module, Module} -> + logSuccess("Reloaded(Beam ~p) Mod:~s and write Success on node:~p", [Reason, Module, Node]); + {error, What} -> + logErrors("Reloaded(Beam ~p) Mod:~s and write Errors on node:~p Reason:~p", [Module, Node, What]) + end; + _ -> + %% File doesn't exist, just load into VM. + case erpc:call(Node, code, load_binary, [Module, undefined, Binary]) of + {module, Module} -> + logSuccess("Reloaded(Beam ~p) Mod:~s Success on node:~p", [Reason, Module, Node]); + {error, What} -> + logErrors("Reloaded(Beam ~p) Mod:~s Errors on node:~p Reason:~p", [Reason, Module, Node, What]) + end + end + end || Node <- Nodes + ], + logSuccess("Reloaded(Beam changed) Mod: ~s on ~p nodes:~p", [Module, NumNodes, Nodes]). + +recompileChangeSrcFile(Iterator, SwSyncNode) -> + case maps:next(Iterator) of + {File, _V, NextIterator} -> + recompileSrcFile(File, SwSyncNode), + recompileChangeSrcFile(NextIterator, SwSyncNode); + _ -> + ok + end. + +erlydtlCompile(SrcFile, Options) -> + F = + fun({outdir, OutDir}, Acc) -> [{out_dir, OutDir} | Acc]; + (OtherOption, Acc) -> [OtherOption | Acc] + end, + DtlOptions = lists:foldl(F, [], Options), + Module = binary_to_atom(lists:flatten(filename:basename(SrcFile, ".dtl") ++ "_dtl")), + Compiler = erlydtl, + Compiler:compile(SrcFile, Module, DtlOptions). + +elixir_compile(SrcFile, Options) -> + Outdir = proplists:get_value(outdir, Options), + Compiler = ':Elixir.Kernel.ParallelCompiler', + Modules = Compiler:files_to_path([SrcFile], Outdir), + Loader = + fun(Module) -> + Outfile = code:which(Module), + Binary = file:read_file(Outfile), + {Module, Binary} + end, + Results = lists:map(Loader, Modules), + {ok, multiple, Results, []}. + +lfe_compile(SrcFile, Options) -> + Compiler = lfe_comp, + Compiler:file(SrcFile, Options). + +getCompileFunAndModuleName(SrcFile) -> + case getFileType(SrcFile) of + erl -> + {fun compile:file/2, binary_to_atom(filename:basename(SrcFile, <<".erl">>))}; + dtl -> + {fun erlydtlCompile/2, list_to_atom(lists:flatten(binary_to_list(filename:basename(SrcFile, <<".dtl">>)) ++ "_dtl"))}; + lfe -> + {fun lfe_compile/2, binary_to_atom(filename:basename(SrcFile, <<".lfe">>))}; + elixir -> + {fun elixir_compile/2, binary_to_atom(filename:basename(SrcFile, <<".ex">>))} + end. + +getObjectCode(Module) -> + case code:get_object_code(Module) of + {Module, B, Filename} -> {B, Filename}; + _ -> {undefined, undefined} + end. + +reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode) -> + case Binary =/= OldBinary of + true -> + %% Try to load the module... + case code:ensure_loaded(Module) of + {module, Module} -> + case code:load_binary(Module, Filename, Binary) of + {module, Module} -> + logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]), + syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled); + {error, What} -> + logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What]) + end; + {error, nofile} -> + case code:load_binary(Module, Filename, Binary) of + {module, Module} -> + logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]), + syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled); + {error, What} -> + logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What]) + end; + {error, embedded} -> + case code:load_file(Module) of %% Module is not yet loaded, load it. + {module, Module} -> ok; + {error, nofile} -> + case code:load_binary(Module, Filename, Binary) of + {module, Module} -> + logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]), + syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled); + {error, What} -> + logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What]) + end + end + end; + _ -> + ignore + end. + +recompileSrcFile(SrcFile, SwSyncNode) -> + %% Get the module, src dir, and options... + RootSrcDir = + case getRootSrcDirFromSrcDir(SrcFile) of + undefined -> + filename:dirname(SrcFile); + RetSrcDir -> + RetSrcDir + end, + CurSrcDir = filename:dirname(SrcFile), + {CompileFun, Module} = getCompileFunAndModuleName(SrcFile), + {OldBinary, Filename} = getObjectCode(Module), + case Module == es_gen_ipc orelse getOptions(RootSrcDir) of + true -> + ignore; + {ok, Options} -> + RightFileDir = binary_to_list(filename:join(CurSrcDir, filename:basename(SrcFile))), + LastOptions = ?IIF(?esCfgSync:getv(?isJustMem), [binary, return | Options], [return | Options]), + case CompileFun(RightFileDir, LastOptions) of + {ok, Module, Binary, Warnings} -> + printResults(Module, RightFileDir, [], Warnings), + reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode), + {ok, [], Warnings}; + {ok, [{ok, Module, Binary, Warnings}], Warnings2} -> + printResults(Module, RightFileDir, [], Warnings ++ Warnings2), + reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode), + {ok, [], Warnings ++ Warnings2}; + {ok, multiple, Results, Warnings} -> + printResults(Module, RightFileDir, [], Warnings), + [reloadIfNecessary(CompiledModule, OldBinary, Binary, Filename, SwSyncNode) || {CompiledModule, Binary} <- Results], + {ok, [], Warnings}; + {ok, OtherModule, _Binary, Warnings} -> + Desc = io_lib:format("Module definition (~p) differs from expected (~s)", [OtherModule, filename:rootname(filename:basename(RightFileDir))]), + Errors = [{RightFileDir, {0, Module, Desc}}], + printResults(Module, RightFileDir, Errors, Warnings), + {ok, Errors, Warnings}; + {error, Errors, Warnings} -> + printResults(Module, RightFileDir, Errors, Warnings), + {ok, Errors, Warnings}; + {ok, Module, Warnings} -> + printResults(Module, RightFileDir, [], Warnings), + {ok, [], Warnings}; + _Err -> + logErrors("compile Mod:~s Errors Reason:~p", [Module, _Err]) + end; + undefined -> + case tryGetModOpts(Module) of + {ok, Options} -> + setOptions(RootSrcDir, Options), + recompileSrcFile(SrcFile, SwSyncNode); + _ -> + case tryGetSrcOpts(CurSrcDir) of + {ok, Options} -> + setOptions(RootSrcDir, Options), + recompileSrcFile(SrcFile, SwSyncNode); + _ -> + logErrors("Unable to determine options for ~s", [SrcFile]) + end + end + end. + +collIncludeCHrls([], AllHrls, CHrls, NewAddMap) -> + case maps:size(NewAddMap) > 0 of + true -> + collIncludeCHrls(maps:keys(NewAddMap), AllHrls, CHrls, #{}); + _ -> + CHrls + end; +collIncludeCHrls([OneHrl | LeftCHrls], AllHrls, CHrls, NewAddMap) -> + {NewCHrls, NNewAddMap} = whoInclude(OneHrl, AllHrls, CHrls, NewAddMap), + collIncludeCHrls(LeftCHrls, AllHrls, NewCHrls, NNewAddMap). + +collIncludeCErls([], _SrcFiles, CSrcs, _NewAddMap) -> + CSrcs; +collIncludeCErls([Hrl | LeftHrl], SrcFiles, CSrcs, NewAddMap) -> + {NewCSrcs, NNewAddMap} = whoInclude(Hrl, SrcFiles, CSrcs, NewAddMap), + collIncludeCErls(LeftHrl, SrcFiles, NewCSrcs, NNewAddMap). + +whoInclude(HrlFile, AllFiles, CFiles, NewAddMap) -> + HrlFileBaseName = filename:basename(HrlFile), + QuoteHrlFileBaseName = <<"\"", HrlFileBaseName/binary, "\"">>, + doMathEveryFile(maps:iterator(AllFiles), QuoteHrlFileBaseName, CFiles, NewAddMap). + +doMathEveryFile(Iterator, HrlFileBaseName, CFiles, NewAddMap) -> + case maps:next(Iterator) of + {OneFile, _V, NextIterator} -> + case file:open(OneFile, [read, binary]) of + {ok, IoDevice} -> + IsInclude = doMathEveryLine(IoDevice, HrlFileBaseName), + file:close(IoDevice), + case IsInclude of + true -> + case maps:is_key(OneFile, CFiles) of + true -> + doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap); + _ -> + doMathEveryFile(NextIterator, HrlFileBaseName, CFiles#{OneFile => 1}, NewAddMap#{OneFile => 1}) + end; + _ -> + doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap) + end; + _ -> + doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap) + end; + _ -> + {CFiles, NewAddMap} + end. + +%% 注释 +%% whoInclude(HrlFile, SrcFiles) -> +%% HrlFileBaseName = filename:basename(HrlFile), +%% Pred = +%% fun(SrcFile, _) -> +%% {ok, Forms} = epp_dodger:parse_file(SrcFile), +%% isInclude(binary_to_list(HrlFileBaseName), Forms) +%% end, +%% maps:filter(Pred, SrcFiles). +%% isInclude(_HrlFile, []) -> +%% false; +%% isInclude(HrlFile, [{tree, attribute, _, {attribute, _, [{_, _, IncludeFile}]}} | Forms]) when is_list(IncludeFile) -> +%% IncludeFileBaseName = filename:basename(IncludeFile), +%% case IncludeFileBaseName of +%% HrlFile -> true; +%% _ -> isInclude(HrlFile, Forms) +%% end; +%% isInclude(HrlFile, [_SomeForm | Forms]) -> +%% isInclude(HrlFile, Forms). + +doMathEveryLine(IoDevice, HrlFileBaseName) -> + case file:read_line(IoDevice) of + {ok, Data} -> + case re:run(Data, HrlFileBaseName) of + nomatch -> + case re:run(Data, <<"->">>) of + nomatch -> + doMathEveryLine(IoDevice, HrlFileBaseName); + _ -> + false + end; + _ -> + true + end; + _ -> + false + end. + +classifyChangeFile([], Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) -> + {Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams}; +classifyChangeFile([OneFile | LeftFile], Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) -> + CurMTimeSec = dateTimeToSec(filelib:last_modified(OneFile)), + case filename:extension(OneFile) of + <<".beam">> -> + BinMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)), + case ColBeams of + #{BinMod := OldMTimeSec} -> + case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of + true -> + classifyChangeFile(LeftFile, [BinMod | Beams], Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams#{BinMod := CurMTimeSec}); + _ -> + classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) + end; + _ -> + classifyChangeFile(LeftFile, [BinMod | Beams], Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams#{BinMod => CurMTimeSec}) + end; + <<".config">> -> + AbsFile = filename:absname(OneFile), + case ColConfigs of + #{AbsFile := OldMTimeSec} -> + case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of + true -> + CfgMod = erlang:binary_to_atom(filename:basename(AbsFile, <<".config">>), utf8), + classifyChangeFile(LeftFile, Beams, [CfgMod | Configs], Hrls, Srcs, ColSrcs, ColHrls, ColConfigs#{AbsFile := CurMTimeSec}, ColBeams); + _ -> + classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) + end; + _ -> + CfgMod = erlang:binary_to_atom(filename:basename(AbsFile, <<".config">>), utf8), + classifyChangeFile(LeftFile, Beams, [CfgMod | Configs], Hrls, Srcs, ColSrcs, ColHrls, ColConfigs#{AbsFile => CurMTimeSec}, ColBeams) + end; + <<".hrl">> -> + AbsFile = filename:absname(OneFile), + case ColHrls of + #{AbsFile := OldMTimeSec} -> + case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of + true -> + classifyChangeFile(LeftFile, Beams, Configs, Hrls#{AbsFile => 1}, Srcs, ColSrcs, ColHrls#{AbsFile := CurMTimeSec}, ColConfigs, ColBeams); + _ -> + classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) + end; + _ -> + classifyChangeFile(LeftFile, Beams, Configs, Hrls#{AbsFile => 1}, Srcs, ColSrcs, ColHrls#{AbsFile => CurMTimeSec}, ColConfigs, ColBeams) + end; + <<>> -> + classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams); + _ -> + AbsFile = filename:absname(OneFile), + case ColSrcs of + #{AbsFile := OldMTimeSec} -> + case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of + true -> + classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs#{AbsFile => 1}, ColSrcs#{AbsFile := CurMTimeSec}, ColHrls, ColConfigs, ColBeams); + _ -> + classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) + end; + _ -> + classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs#{AbsFile => 1}, ColSrcs#{AbsFile => CurMTimeSec}, ColHrls, ColConfigs, ColBeams) + end + end. + +fileSyncPath(ExecName) -> + case code:priv_dir(?MODULE) of + {error, _} -> + case code:which(?MODULE) of + Filename when is_list(Filename) -> + filename:join([filename:dirname(filename:dirname(Filename)), "priv", ExecName]); + _ -> + filename:join("../priv", ExecName) + end; + Dir -> + filename:join(Dir, ExecName) + end. + +dateTimeToSec(DateTime) -> + if + DateTime == 0 -> + 0; + true -> + erlang:universaltime_to_posixtime(DateTime) + end. + +debugInfoKeyFun() -> + case ?esCfgSync:getv(?debugInfoKeyFun) of + undefined -> + {debug_info_key, undefined}; + {Mod, Fun} -> + Mod:Fun() + end. +%% ************************************************* utils end ******************************************************* diff --git a/src/erlSync.erl b/src/erlSync.erl deleted file mode 100644 index b797a97..0000000 --- a/src/erlSync.erl +++ /dev/null @@ -1,61 +0,0 @@ --module(erlSync). - --export([ - start/0, - stop/0, - run/0, - pause/0, - curInfo/0, - setLog/1, - getLog/0, - getOnsync/0, - setOnsync/0, - setOnsync/1, - swSyncNode/1 -]). - -start() -> - application:ensure_all_started(erlSync). - -stop() -> - application:stop(erlSync). - -run() -> - case start() of - {ok, _Started} -> - esSyncSrv:unpause(), - ok; - {error, Reason} -> - Msg = io_lib:format("start erlSync error:~p~n", [Reason]), - esUtils:logErrors(Msg) - end. - -pause() -> - esSyncSrv:pause(). - -swSyncNode(IsSync) -> - run(), - esSyncSrv:swSyncNode(IsSync), - ok. - -curInfo() -> - esSyncSrv:curInfo(). - -setLog(Val) -> - esSyncSrv:setLog(Val). - -getLog() -> - esSyncSrv:getLog(). - -getOnsync() -> - esSyncSrv:getOnsync(). - -setOnsync() -> - esSyncSrv:setOnsync(undefined). - -setOnsync(Fun) -> - esSyncSrv:setOnsync(Fun). - - - - diff --git a/src/erlSync_app.erl b/src/erlSync_app.erl deleted file mode 100644 index 417f7c2..0000000 --- a/src/erlSync_app.erl +++ /dev/null @@ -1,10 +0,0 @@ --module(erlSync_app). --behaviour(application). - --export([start/2, stop/1]). - -start(_StartType, _StartArgs) -> - erlSync_sup:start_link(). - -stop(_State) -> - ok. diff --git a/src/erlSync_sup.erl b/src/erlSync_sup.erl deleted file mode 100644 index c298203..0000000 --- a/src/erlSync_sup.erl +++ /dev/null @@ -1,27 +0,0 @@ --module(erlSync_sup). --behaviour(supervisor). - --export([ - start_link/0 - , init/1 -]). - --define(SERVER, ?MODULE). --define(ChildSpec(I, Type), #{id => I, start => {I, start_link, []}, restart => permanent, shutdown => 5000, type => Type, modules => [I]}). - -start_link() -> - supervisor:start_link({local, ?SERVER}, ?MODULE, []). - -%% sup_flags() = #{strategy => strategy(), % optional -%% intensity => non_neg_integer(), % optional -%% period => pos_integer()} % optional -%% child_spec() = #{id => child_id(), % mandatory -%% start => mfargs(), % mandatory -%% restart => restart(), % optional -%% shutdown => shutdown(), % optional -%% type => worker(), % optional -%% modules => modules()} % optional -init([]) -> - SupFlags = #{strategy => one_for_one, intensity => 5, period => 10}, - ChildSpecs = [?ChildSpec(esSyncSrv, worker)], - {ok, {SupFlags, ChildSpecs}}. diff --git a/src/sync/esSyncSrv.erl b/src/sync/esSyncSrv.erl deleted file mode 100644 index 6281901..0000000 --- a/src/sync/esSyncSrv.erl +++ /dev/null @@ -1,234 +0,0 @@ --module(esSyncSrv). - --behaviour(gen_server). - --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_server callbacks --export([ - init/1, - handle_call/3, - handle_cast/2, - handle_info/2, - terminate/2 -]). - --define(SERVER, ?MODULE). - --record(state, { - status = running :: running | pause - , srcFiles = #{} :: map() - , onsyncFun = undefined - , swSyncNode = false - , sockMod = undefined - , sock = undefined -}). - -%% ************************************ API start *************************** -rescan() -> - gen_server:cast(?SERVER, miRescan), - esUtils:logSuccess("start rescaning source files..."), - ok. - -unpause() -> - gen_server:cast(?SERVER, miUnpause), - ok. - -pause() -> - gen_server:cast(?SERVER, miPause), - esUtils:logSuccess("Pausing erlSync. Call erlSync:run() to restart"), - ok. - -curInfo() -> - gen_server:call(?SERVER, miCurInfo). - -setLog(T) when ?LOG_ON(T) -> - esUtils:setEnv(log, T), - 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_server:cast(?SERVER, {miSyncNode, IsSync}), - ok. - -getOnsync() -> - gen_server:call(?SERVER, miGetOnsync). - -setOnsync(Fun) -> - gen_server:call(?SERVER, {miSetOnsync, Fun}). - -%% ************************************ API end *************************** - -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). - -init([]) -> - erlang:process_flag(trap_exit, true), - esUtils:loadCfg(), - erlang:send_after(0, self(), doAfter), - {ok, #state{}}. - -handle_call(miGetOnsync, _, #state{onsyncFun = OnSync} = State) -> - {reply, OnSync, State}; -handle_call({miSetOnsync, Fun}, _, State) -> - {reply, ok, State#state{onsyncFun = Fun}}; -handle_call(miCurInfo, _, State) -> - {reply, {erlang:get(), State}, State}; -handle_call(_Request, _, State) -> - {reply, ok, State}. - -handle_cast(miPause, State) -> - {noreply, State#state{status = pause}}; -handle_cast(miUnpause, State) -> - {noreply, State#state{status = running}}; -handle_cast({miSyncNode, IsSync}, State) -> - case IsSync of - true -> - {noreply, State#state{swSyncNode = true}}; - _ -> - {noreply, State#state{swSyncNode = false}} - end; -handle_cast(miRescan, State) -> - SrcFiles = esUtils:collSrcFiles(false), - {noreply, State#state{srcFiles = SrcFiles}}; -handle_cast(_Msg, State) -> - esUtils:logSuccess("recv unexpect cast msg..."), - {noreply, State}. - -handle_info({tcp, _Socket, Data}, #state{status = running, srcFiles = SrcFiles, onsyncFun = OnsyncFun, swSyncNode = SwSyncNode} = State) -> - FileList = binary:split(Data, <<"\r\n">>, [global]), - %% 收集改动了beam hrl src 文件 然后执行相应的逻辑 - {Beams, Hrls, Srcs} = esUtils:classifyChangeFile(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), - {noreply, 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, - {noreply, State} - end; -handle_info({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), - esUtils:logSuccess("erlSync connect fileSync success... ~n"), - case ?esCfgSync:getv(?compileCmd) of - undefined -> - %% 然后收集一下监听目录下的src文件 - SrcFiles = esUtils:collSrcFiles(true), - {noreply, State#state{status = running, sock = Sock, srcFiles = SrcFiles}}; - _ -> - {noreply, State#state{status = running}} - end; - {error, closed} -> - Msg = io_lib:format("error, closed listen sock error ~p~n", [closed]), - esUtils:logErrors(Msg), - {stop, normal, State}; - {error, Reason} -> - Msg = io_lib:format("listen sock error ~p~n", [Reason]), - esUtils:logErrors(Msg), - {stop, Reason, State} - end; -handle_info({tcp_closed, _Socket}, _State) -> - Msg = io_lib:format("esSyncSrv receive tcp_closed ~n", []), - esUtils:logErrors(Msg), - {noreply, _State}; -handle_info({tcp_error, _Socket, Reason}, _State) -> - Msg = io_lib:format("esSyncSrv receive tcp_error Reason:~p ~n", [Reason]), - esUtils:logErrors(Msg), - {noreply, _State}; -handle_info(doAfter, 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} -> - CmtStr = "start " ++ esUtils:fileSyncPath("fileSync.exe") ++ " ./ " ++ integer_to_list(ListenPort), - os:cmd(CmtStr); - _ -> - CmtStr = esUtils:fileSyncPath("fileSync") ++ " ./ " ++ integer_to_list(ListenPort), - os:cmd(CmtStr) - end end), - erlang:send_after(4000, self(), waitConnOver), - {noreply, State#state{sockMod = SockMod}}; - {error, Reason} -> - Msg = io_lib:format("init prim_inet:async_accept error ~p~n", [Reason]), - esUtils:logErrors(Msg), - {stop, waitConnOver, State} - end; - {error, Reason} -> - Msg = io_lib:format("failed to listen on ~p - ~p (~s) ~n", [ListenPort, Reason, inet:format_error(Reason)]), - esUtils:logErrors(Msg), - {stop, waitConnOver, State} - end; -handle_info(waitConnOver, #state{sock = undefined} = State) -> - Msg = io_lib:format("failed to start fileSync~n", []), - esUtils:logErrors(Msg), - {stop, waitConnOver, State}; -handle_info(_Msg, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. diff --git a/src/sync/esUtils.erl b/src/sync/esUtils.erl deleted file mode 100644 index 18f85fb..0000000 --- a/src/sync/esUtils.erl +++ /dev/null @@ -1,886 +0,0 @@ --module(esUtils). - --include("erlSync.hrl"). - --compile(inline). --compile({inline_size, 128}). --compile([export_all, nowarn_export_all]). - -getModSrcDir(Module) -> - case code:is_loaded(Module) of - {file, _} -> - try - %% Get some module info... - Props = Module:module_info(compile), - Source = proplists:get_value(source, Props, ""), - %% Ensure that the file exists, is a decendent of the tree, and how to deal with that - IsFile = filelib:is_regular(Source), - IsDescendant = isDescendent(Source), - Descendant = ?esCfgSync:getv(?descendant), - LastSource = - case {IsFile, IsDescendant, Descendant} of - %% is file and descendant, we're good to go - {true, true, _} -> Source; - %% is not a descendant, but we allow them, so good to go - {true, false, allow} -> Source; - %% is not a descendant, and we fix non-descendants, so let's fix it - {_, false, fix} -> fixDescendantSource(Source, IsFile); - %% Anything else, and we don't know what to do, so let's just bail. - _ -> undefined - end, - case LastSource of - undefined -> - undefined; - _ -> - %% Get the source dir... - Dir = filename:dirname(LastSource), - getSrcDir(Dir) - end - catch _ : _ -> - undefined - end; - _ -> - undefined - end. - -getModOptions(Module) -> - case code:is_loaded(Module) of - {file, _} -> - try - Props = Module:module_info(compile), - BeamDir = filename:dirname(code:which(Module)), - Options1 = proplists:get_value(options, Props, []), - %% transform `outdir' - Options2 = transformOutdir(BeamDir, Options1), - Options3 = ensureInclude(Options2), - %% transform the include directories - Options4 = transformAllIncludes(Module, BeamDir, Options3), - %% maybe_add_compile_info - Options5 = maybeAddCompileInfo(Options4), - %% add filetype to options (DTL, LFE, erl, etc) - Options6 = addFileType(Module, Options5), - {ok, Options6} - catch ExType:Error -> - Msg = [io_lib:format("~p:0: ~p looking for options: ~p. ~n", [Module, ExType, Error])], - logWarnings(Msg), - undefined - end; - _ -> - undefined - end. - -tryGetModOptions(Module) -> - try - Props = Module:module_info(compile), - BeamDir = filename:dirname(code:which(Module)), - Options1 = proplists:get_value(options, Props, []), - %% transform `outdir' - Options2 = transformOutdir(BeamDir, Options1), - Options3 = ensureInclude(Options2), - %% transform the include directories - Options4 = transformAllIncludes(Module, BeamDir, Options3), - %% maybe_add_compile_info - Options5 = maybeAddCompileInfo(Options4), - %% add filetype to options (DTL, LFE, erl, etc) - Options6 = addFileType(Module, Options5), - {ok, Options6} - catch _ExType:_Error -> - 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; - _ -> - BaseName = filename:basename(SrcDir), - IsBaseSrcDir = BaseName == ?rootSrcDir, - case NewDirName =/= SrcDir andalso not IsBaseSrcDir of - true -> - tryGetSrcOptions(NewDirName); - _ when IsBaseSrcDir -> - try filelib:fold_files(SrcDir, ".*\\.(erl|dtl|lfe|ex)$", true, - fun(OneFiles, Acc) -> - Mod = erlang:binary_to_atom(filename:basename(OneFiles, filename:extension(OneFiles)), utf8), - case tryGetModOptions(Mod) of - {ok, _Options} = Opts -> - throw(Opts); - _ -> - Acc - end - end, undefined) - catch - {ok, _Options} = Opts -> - Opts; - _ExType:_Error -> - Msg = [io_lib:format("looking src options error ~p:~p. ~n", [_ExType, _Error])], - logWarnings(Msg), - undefined - end; - _ -> - undefined - end - end. - -transformOutdir(BeamDir, Options) -> - [{outdir, BeamDir} | proplists:delete(outdir, Options)]. - -ensureInclude(Options) -> - case proplists:get_value(i, Options) of - undefined -> [{i, "include"} | Options]; - _ -> Options - end. - -transformAllIncludes(Module, BeamDir, Options) -> - [begin - case Opt of - {i, IncludeDir} -> - {ok, SrcDir} = getModSrcDir(Module), - {ok, IncludeDir2} = determineIncludeDir(IncludeDir, BeamDir, SrcDir), - {i, IncludeDir2}; - _ -> - Opt - end - end || Opt <- Options]. - -maybeAddCompileInfo(Options) -> - case lists:member(compile_info, Options) of - true -> Options; - false -> addCompileInfo(Options) - end. - -addCompileInfo(Options) -> - CompInfo = [{K, V} || {K, V} <- Options, lists:member(K, [outdir, i])], - [{compile_info, CompInfo} | Options]. - -addFileType(Module, Options) -> - Type = getFileType(Module), - [{type, Type} | Options]. - -%% This will check if the given module or source file is an ErlyDTL template. -%% Currently, this is done by checking if its reported source path ends with -%% ".dtl.erl". -getFileType(Module) when is_atom(Module) -> - Props = Module:module_info(compile), - Source = proplists:get_value(source, Props, ""), - getFileType(Source); - -getFileType(Source) -> - Ext = filename:extension(Source), - Root = filename:rootname(Source), - SecondExt = filename:extension(Root), - case Ext of - <<".erl">> when SecondExt =:= <<".dtl">> -> dtl; - <<".dtl">> -> dtl; - <<".erl">> -> erl; - <<".lfe">> -> lfe; - <<".ex">> -> elixir; - ".erl" when SecondExt =:= ".dtl" -> dtl; - ".dtl" -> dtl; - ".erl" -> erl; - ".lfe" -> lfe; - ".ex" -> elixir - end. - -%% This will search back to find an appropriate include directory, by -%% searching further back than "..". Instead, it will extract the basename -%% (probably "include" from the include pathfile, and then search backwards in -%% the directory tree until it finds a directory with the same basename found -%% above. -determineIncludeDir(IncludeDir, BeamDir, SrcDir) -> - IncludeBase = filename:basename(IncludeDir), - case determineIncludeDirFromBeamDir(IncludeBase, IncludeDir, BeamDir) of - {ok, _Dir} = RetD -> RetD; - undefined -> - {ok, Cwd} = file:get_cwd(), - % Cwd2 = normalizeCaseWindowsDir(Cwd), - % SrcDir2 = normalizeCaseWindowsDir(SrcDir), - % IncludeBase2 = normalizeCaseWindowsDir(IncludeBase), - case findIncludeDirFromAncestors(SrcDir, Cwd, IncludeBase) of - {ok, _Dir} = RetD -> RetD; - undefined -> {ok, IncludeDir} %% Failed, just stick with original - end - end. - -%% First try to see if we have an include file alongside our ebin directory, which is typically the case -determineIncludeDirFromBeamDir(IncludeBase, IncludeDir, BeamDir) -> - BeamBasedIncDir = filename:join(filename:dirname(BeamDir), IncludeBase), - case filelib:is_dir(BeamBasedIncDir) of - true -> {ok, BeamBasedIncDir}; - false -> - BeamBasedIncDir2 = filename:join(filename:dirname(BeamDir), IncludeDir), - case filelib:is_dir(BeamBasedIncDir2) of - true -> {ok, BeamBasedIncDir2}; - _ -> - undefined - end - end. - -%% get the src dir -getRootSrcDirFromSrcDir(SrcDir) -> - NewDirName = filename:dirname(SrcDir), - BaseName = filename:basename(SrcDir), - case BaseName of - ?rootSrcDir -> - NewDirName; - _ -> - case NewDirName =/= SrcDir of - true -> - getRootSrcDirFromSrcDir(NewDirName); - _ -> - undefined - end - end. - - -%% Then we dig back through the parent directories until we find our include directory -findIncludeDirFromAncestors(Cwd, Cwd, _) -> undefined; -findIncludeDirFromAncestors("/", _, _) -> undefined; -findIncludeDirFromAncestors(".", _, _) -> undefined; -findIncludeDirFromAncestors("", _, _) -> undefined; -findIncludeDirFromAncestors(Dir, Cwd, IncludeBase) -> - NewDirName = filename:dirname(Dir), - AttemptDir = filename:join(NewDirName, IncludeBase), - case filelib:is_dir(AttemptDir) of - true -> - {ok, AttemptDir}; - false -> - case NewDirName =/= Dir of - true -> - findIncludeDirFromAncestors(NewDirName, Cwd, IncludeBase); - _ -> - undefined - end - end. - -% normalizeCaseWindowsDir(Dir) -> -% case os:type() of -% {win32, _} -> Dir; %string:to_lower(Dir); -% {unix, _} -> Dir -% end. - -%% This is an attempt to intelligently fix paths in modules when a -%% release is moved. Essentially, it takes a module name and its original path -%% from Module:module_info(compile), say -%% "/some/original/path/site/src/pages/somepage.erl", and then breaks down the -%% path one by one prefixing it with the current working directory until it -%% either finds a match, or fails. If it succeeds, it returns the Path to the -%% new Source file. -fixDescendantSource([], _IsFile) -> - undefined; -fixDescendantSource(Path, IsFile) -> - {ok, Cwd} = file:get_cwd(), - PathParts = filename:split(Path), - case makeDescendantSource(PathParts, Cwd) of - undefined -> case IsFile of true -> Path; _ -> undefined end; - FoundPath -> FoundPath - end. - -makeDescendantSource([], _Cwd) -> - undefined; -makeDescendantSource([_ | T], Cwd) -> - PathAttempt = filename:join([Cwd | T]), - case filelib:is_regular(PathAttempt) of - true -> PathAttempt; - false -> makeDescendantSource(T, Cwd) - end. - -isDescendent(Path) -> - {ok, Cwd} = file:get_cwd(), - lists:sublist(Path, length(Cwd)) == Cwd. - -%% @private Find the src directory for the specified Directory; max 15 iterations -getSrcDir(Dir) -> - getSrcDir(Dir, 15). - -getSrcDir(_Dir, 0) -> - undefined; -getSrcDir(Dir, Ctr) -> - HasCode = filelib:wildcard("*.erl", Dir) /= [] orelse - filelib:wildcard("*.hrl", Dir) /= [] orelse - filelib:wildcard("*.dtl", Dir) /= [] orelse - filelib:wildcard("*.lfe", Dir) /= [] orelse - filelib:wildcard("*.ex", Dir) /= [], - if - HasCode -> {ok, Dir}; - true -> getSrcDir(filename:dirname(Dir), Ctr - 1) - end. - -mergeExtraDirs(IsAddPath) -> - case ?esCfgSync:getv(?extraDirs) of - undefined -> - {[], [], []}; - ExtraList -> - FunMerge = - fun(OneExtra, {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 -> - RootSrcDir = - case getRootSrcDirFromSrcDir(OneFiles) of - undefined -> - filename:dirname(OneFiles); - RetSrcDir -> - RetSrcDir - end, - case getOptions(RootSrcDir) of - undefined -> - Mod = erlang:binary_to_atom(filename:basename(OneFiles, filename:extension(OneFiles)), utf8), - case getModOptions(Mod) of - {ok, Options} -> - setOptions(RootSrcDir, Options); - _ -> - ignore - end; - _ -> - ignore - end, - Acc#{OneFiles => 1}; - _ -> - Acc - end; - _ -> - Acc - end - end, #{}), - - FunCollAdds = - fun(OneDir, FilesAcc) -> - filelib:fold_files(case is_list(OneDir) of true -> list_to_binary(OneDir); _ -> - OneDir end, ".*\\.(erl|dtl|lfe|ex)$", true, fun(OneFiles, Acc) -> - Acc#{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 - {ok, Value} -> - Value; - _ -> - Default - end. - -setEnv(Var, Val) -> - ok = application:set_env(erlSync, Var, Val). - -logSuccess(Message) -> - canLog(success) andalso error_logger:info_msg(lists:flatten(Message)). - -logErrors(Message) -> - canLog(errors) andalso error_logger:error_msg(lists:flatten(Message)). - -logWarnings(Message) -> - canLog(warnings) andalso error_logger:warning_msg(lists:flatten(Message)). - -canLog(MsgType) -> - case esSyncSrv:getLog() of - true -> true; - all -> true; - none -> false; - false -> false; - skip_success -> MsgType == errors orelse MsgType == warnings; - L when is_list(L) -> lists:member(MsgType, L); - _ -> false - end. - -%% 注意 map类型的数据不能当做key --type key() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple(). --type value() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple() | map(). - --spec load(term(), [{key(), value()}]) -> ok. -load(Module, KVs) -> - Forms = forms(Module, KVs), - {ok, Module, Bin} = compile:forms(Forms), - code:soft_purge(Module), - {module, Module} = code:load_binary(Module, atom_to_list(Module), Bin), - ok. - -forms(Module, KVs) -> - %% -module(Module). - Mod = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]), - %% -export([getv/0]). - ExportList = [erl_syntax:arity_qualifier(erl_syntax:atom(getv), erl_syntax:integer(1))], - Export = erl_syntax:attribute(erl_syntax:atom(export), [erl_syntax:list(ExportList)]), - %% getv(K) -> V - Function = erl_syntax:function(erl_syntax:atom(getv), lookup_clauses(KVs, [])), - [erl_syntax:revert(X) || X <- [Mod, Export, Function]]. - -lookup_clause(Key, Value) -> - Var = erl_syntax:abstract(Key), - Body = erl_syntax:abstract(Value), - erl_syntax:clause([Var], [], [Body]). - -lookup_clause_anon() -> - Var = erl_syntax:variable("_"), - Body = erl_syntax:atom(undefined), - erl_syntax:clause([Var], [], [Body]). - -lookup_clauses([], Acc) -> - lists:reverse(lists:flatten([lookup_clause_anon() | Acc])); -lookup_clauses([{Key, Value} | T], Acc) -> - lookup_clauses(T, [lookup_clause(Key, Value) | Acc]). - -getOptions(SrcDir) -> - case erlang:get(SrcDir) of - undefined -> - undefined; - Options -> - {ok, Options} - end. - -setOptions(SrcDir, Options) -> - case erlang:get(SrcDir) of - undefined -> - erlang:put(SrcDir, Options); - OldOptions -> - NewOptions = - case lists:keytake(compile_info, 1, Options) of - {value, {compile_info, ValList1}, Options1} -> - case lists:keytake(compile_info, 1, OldOptions) of - {value, {compile_info, ValList2}, Options2} -> - [{compile_info, lists:usort(ValList1 ++ ValList2)} | lists:usort(Options1 ++ Options2)]; - _ -> - lists:usort(Options ++ OldOptions) - end; - _ -> - lists:usort(Options ++ OldOptions) - end, - erlang:put(SrcDir, NewOptions) - end. - -loadCfg() -> - KVs = [{Key, esUtils:getEnv(Key, DefVal)} || {Key, DefVal} <- ?CfgList], - esUtils:load(?esCfgSync, KVs). - -%% ******************************* 加载与编译相关 ********************************************************************** -errorNoFile(Module) -> - Msg = io_lib:format("~p Couldn't load module: nofile ~n", [Module]), - esUtils:logWarnings([Msg]). - -printResults(_Module, SrcFile, [], []) -> - Msg = io_lib:format("~s Recompiled ~n", [SrcFile]), - esUtils:logSuccess(lists:flatten(Msg)); -printResults(_Module, SrcFile, [], Warnings) -> - formatErrors(fun esUtils:logWarnings/1, SrcFile, [], Warnings), io_lib:format("~s Recompiled with ~p warnings~n", [SrcFile, length(Warnings)]); -printResults(_Module, SrcFile, Errors, Warnings) -> - formatErrors(fun esUtils:logErrors/1, SrcFile, Errors, Warnings). - -%% @private Print error messages in a pretty and user readable way. -formatErrors(LogFun, File, Errors, Warnings) -> - AllErrors1 = lists:sort(lists:flatten([X || {_, X} <- Errors])), - AllErrors2 = [{Line, "Error", Module, Description} || {Line, Module, Description} <- AllErrors1], - AllWarnings1 = lists:sort(lists:flatten([X || {_, X} <- Warnings])), - AllWarnings2 = [{Line, "Warning", Module, Description} || {Line, Module, Description} <- AllWarnings1], - Everything = lists:sort(AllErrors2 ++ AllWarnings2), - FPck = - fun({Line, Prefix, Module, ErrorDescription}) -> - Msg = formatError(Module, ErrorDescription), - LogMsg = io_lib:format("~s:~p: ~s: ~s~n", [File, Line, Prefix, Msg]), - LogFun(LogMsg) - end, - [FPck(X) || X <- Everything], - ok. - -formatError(Module, ErrorDescription) -> - case erlang:function_exported(Module, format_error, 1) of - true -> Module:format_error(ErrorDescription); - false -> io_lib:format("~s ~n", [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~n", [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~n", [Module]), - esUtils:logSuccess(Msg); - {error, What} -> - Msg = io_lib:format("Reloaded(Beam changed) Mod:~s Errors Reason:~p~n", [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~n", [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~n", [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~n", [Module, Node]), - esUtils:logSuccess(Msg); - {error, What} -> - Msg = io_lib:format("Reloaded(Beam changed) Mod:~s and write Errors on node:~p Reason:~p~n", [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~n", [Module, Node]), - esUtils:logSuccess(Msg); - {error, What} -> - Msg = io_lib:format("Reloaded(Beam changed) Mod:~s Errors on node:~p Reason:~p~n", [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 = erlang:binary_to_atom(lists:flatten(filename:basename(SrcFile, ".dtl") ++ "_dtl"), utf8), - Compiler = erlydtl, - Compiler:compile(SrcFile, Module, DtlOptions). - -elixir_compile(SrcFile, Options) -> - Outdir = proplists:get_value(outdir, Options), - Compiler = ':Elixir.Kernel.ParallelCompiler', - Modules = Compiler:files_to_path([SrcFile], Outdir), - Loader = - fun(Module) -> - Outfile = code:which(Module), - Binary = file:read_file(Outfile), - {Module, Binary} - end, - Results = lists:map(Loader, Modules), - {ok, multiple, Results, []}. - -lfe_compile(SrcFile, Options) -> - Compiler = lfe_comp, - Compiler:file(SrcFile, Options). - -getCompileFunAndModuleName(SrcFile) -> - case esUtils:getFileType(SrcFile) of - erl -> - {fun compile:file/2, erlang:binary_to_atom(filename:basename(SrcFile, <<".erl">>), utf8)}; - dtl -> - {fun erlydtlCompile/2, list_to_atom(lists:flatten(binary_to_list(filename:basename(SrcFile, <<".dtl">>)) ++ "_dtl"))}; - lfe -> - {fun lfe_compile/2, erlang:binary_to_atom(filename:basename(SrcFile, <<".lfe">>), utf8)}; - elixir -> - {fun elixir_compile/2, erlang:binary_to_atom(filename:basename(SrcFile, <<".ex">>), utf8)} - 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~n", [Module]), - esUtils:logSuccess(Msg); - {error, What} -> - Msg = io_lib:format("Reloaded(Beam changed) Mod:~s Errors Reason:~p~n", [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... - RootSrcDir = - case getRootSrcDirFromSrcDir(SrcFile) of - undefined -> - filename:dirname(SrcFile); - RetSrcDir -> - RetSrcDir - end, - CurSrcDir = filename:dirname(SrcFile), - {CompileFun, Module} = getCompileFunAndModuleName(SrcFile), - {OldBinary, Filename} = getObjectCode(Module), - case getOptions(RootSrcDir) of - {ok, Options} -> - RightFileDir = binary_to_list(filename:join(CurSrcDir, 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) ~n", [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}; - _Err -> - Msg = io_lib:format("compile Mod:~s Errors Reason:~p ~n", [Module, _Err]), - esUtils:logErrors(Msg) - end; - undefined -> - case esUtils:tryGetModOptions(Module) of - {ok, Options} -> - setOptions(RootSrcDir, Options), - recompileSrcFile(SrcFile, SwSyncNode); - _ -> - case esUtils:tryGetSrcOptions(CurSrcDir) of - {ok, Options} -> - setOptions(RootSrcDir, Options), - recompileSrcFile(SrcFile, SwSyncNode); - _ -> - Msg = io_lib:format("Unable to determine options for ~s~n", [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). - -whoInclude(HrlFile, SrcFiles) -> - HrlFileBaseName = filename:basename(HrlFile), - Pred = - fun(SrcFile, _) -> - case file:open(SrcFile, [read, binary]) of - {ok, IoDevice} -> - IsInclude = doMathEveryLine(IoDevice, HrlFileBaseName), - file:close(IoDevice), - IsInclude; - _ -> - false - end - end, - maps:filter(Pred, SrcFiles). -doMathEveryLine(IoDevice, HrlFileBaseName) -> - case file:read_line(IoDevice) of - {ok, Data} -> - case re:run(Data, HrlFileBaseName) of - nomatch -> - case re:run(Data, <<"->">>) of - nomatch -> - doMathEveryLine(IoDevice, HrlFileBaseName); - _ -> - false - end; - _ -> - true - end; - _ -> - false - end. - -classifyChangeFile([], Beams, Hrls, Srcs) -> - {Beams, Hrls, Srcs}; -classifyChangeFile([OneFile | LeftFile], Beams, Hrls, Srcs) -> - case filename:extension(OneFile) of - <<".beam">> -> - Module = erlang:binary_to_atom(filename:basename(OneFile, <<".beam">>), utf8), - classifyChangeFile(LeftFile, [Module | Beams], Hrls, Srcs); - <<".hrl">> -> - classifyChangeFile(LeftFile, Beams, [OneFile | Hrls], Srcs); - <<>> -> - classifyChangeFile(LeftFile, Beams, Hrls, Srcs); - _ -> - classifyChangeFile(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. - -fileSyncPath(ExecName) -> - case code:priv_dir(?MODULE) of - {error, _} -> - case code:which(?MODULE) of - Filename when is_list(Filename) -> - filename:join([filename:dirname(filename:dirname(Filename)), "priv", ExecName]); - _ -> - filename:join("../priv", ExecName) - end; - Dir -> - filename:join(Dir, ExecName) - end.