-module(esUtils).
|
|
-include("erlSync.hrl").
|
|
|
|
-compile(inline).
|
|
-compile({inline_size, 128}).
|
|
|
|
-export([
|
|
getModSrcDir/1,
|
|
getModOptions/1,
|
|
getFileType/1,
|
|
getSrcDir/1,
|
|
wildcard/2,
|
|
getEnv/2,
|
|
setEnv/2,
|
|
load/2,
|
|
logSuccess/1,
|
|
logErrors/1,
|
|
logWarnings/1,
|
|
getSystemModules/0
|
|
]).
|
|
|
|
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);
|
|
%% 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:Stacktrace ->
|
|
Msg = [io_lib:format("~p:0: ~p looking for options: ~p. Stack: ~p~n", [Module, ExType, Error, Stacktrace])],
|
|
logWarnings(Msg),
|
|
{ok, []}
|
|
end;
|
|
_ ->
|
|
{ok, []}
|
|
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) ->
|
|
[transformInclude(Module, BeamDir, Opt) || Opt <- Options].
|
|
|
|
transformInclude(Module, BeamDir, {i, IncludeDir}) ->
|
|
{ok, SrcDir} = getModSrcDir(Module),
|
|
{ok, IncludeDir2} = determineIncludeDir(IncludeDir, BeamDir, SrcDir),
|
|
{i, IncludeDir2};
|
|
transformInclude(_, _, Other) ->
|
|
Other.
|
|
|
|
maybeAddCompileInfo(Options) ->
|
|
case lists:member(predetermined, 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) when is_list(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
|
|
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, BeamDir) of
|
|
{ok, D} -> {ok, D};
|
|
undefined ->
|
|
{ok, Cwd} = file:get_cwd(),
|
|
% 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.
|
|
|
|
%% 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
|
|
true -> {ok, BeamBasedIncDir};
|
|
false -> undefined
|
|
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(Cwd, IncludeBase, Dir) ->
|
|
AttemptDir = filename:join(filename:dirname(Dir), IncludeBase),
|
|
case filelib:is_dir(AttemptDir) of
|
|
true ->
|
|
{ok, AttemptDir};
|
|
false ->
|
|
findIncludeDirFromAncestors(Cwd, IncludeBase, filename:dirname(Dir))
|
|
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([]) ->
|
|
undefined;
|
|
fixDescendantSource(Path) ->
|
|
{ok, Cwd} = file:get_cwd(),
|
|
PathParts = filename:split(Path),
|
|
case makeDescendantSource(Cwd, PathParts) of
|
|
undefined -> Path;
|
|
FoundPath -> FoundPath
|
|
end.
|
|
|
|
makeDescendantSource(_Cwd, []) ->
|
|
undefined;
|
|
makeDescendantSource(Cwd, [_ | T]) ->
|
|
PathAttempt = filename:join([Cwd | T]),
|
|
case filelib:is_regular(PathAttempt) of
|
|
true -> PathAttempt;
|
|
false -> makeDescendantSource(Cwd, T)
|
|
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.
|
|
|
|
%% Return all files in a directory matching a regex.
|
|
wildcard(Dir, Regex) ->
|
|
filelib:fold_files(Dir, Regex, true, fun(Y, Acc) -> [Y | Acc] 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 esScanner: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.
|
|
|
|
%% 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
|
|
],
|
|
FAppMod =
|
|
fun(App) ->
|
|
case application:get_key(App, modules) of
|
|
{ok, Modules} -> Modules;
|
|
_Other -> []
|
|
end
|
|
end,
|
|
lists:flatten([FAppMod(X) || X <- Apps]).
|
|
|
|
%% 注意 map类型的数据不能当做key
|
|
-type key() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple().
|
|
-type value() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple() | map().
|
|
|
|
-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]).
|
|
|