erlang自动编译与加载
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 

323 rindas
11 KiB

-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]).