-module(eMake).
|
|
%% 多进程编译,修改自otp/lib/tools/src/make.erl
|
|
%% 解析Emakefile,根据获取{mods, options}列表,
|
|
%% 按照次序编译每项(解决编译顺序的问题)
|
|
%% 其中mods也可以包含多个模块,当大于1个时,
|
|
%% 可以启动多个process进行编译,从而提高编译速度.
|
|
|
|
-include_lib("kernel/include/file.hrl").
|
|
|
|
-export([
|
|
main/1
|
|
, all/3
|
|
]).
|
|
|
|
-define(MakeOpts, [noexec, load, netload, noload]).
|
|
|
|
main(Args) ->
|
|
io:format("~p~n", [Args]),
|
|
|
|
case Args of
|
|
[] ->
|
|
all(max(1, erlang:system_info(schedulers) - 1), "./Emakefile", []);
|
|
[EMakeFileOrCnt] ->
|
|
try list_to_integer(EMakeFileOrCnt) of
|
|
Cnt ->
|
|
all(max(1, Cnt), "./Emakefile", [])
|
|
catch _:_ ->
|
|
all(max(1, erlang:system_info(schedulers) - 1), EMakeFileOrCnt, [])
|
|
end;
|
|
[EMakeFile, CntStr] ->
|
|
all(max(1, list_to_integer(CntStr)), EMakeFile, []);
|
|
[EMakeFile, CntStr, OptsStr] ->
|
|
{ok, Opts} = strToTerm(OptsStr),
|
|
all(max(1, list_to_integer(CntStr)), EMakeFile, Opts)
|
|
end.
|
|
|
|
all(Worker, EMakeFile, Opts) ->
|
|
{MakeOpts, CompileOpts} = splitOpts(Opts, [], []),
|
|
case readEMakefile(EMakeFile, CompileOpts) of
|
|
Files when is_list(Files) ->
|
|
do_make_files(Worker, Files, MakeOpts);
|
|
error ->
|
|
error
|
|
end.
|
|
|
|
splitOpts([], Make, Compile) ->
|
|
{Make, lists:reverse(Compile)};
|
|
splitOpts([H | T], Make, Compile) ->
|
|
case lists:member(H, ?MakeOpts) of
|
|
true ->
|
|
splitOpts(T, [H | Make], Compile);
|
|
false ->
|
|
splitOpts(T, Make, [H | Compile])
|
|
end.
|
|
|
|
|
|
%% term反序列化, string转换为term
|
|
strToTerm(String) ->
|
|
case erl_scan:string(String ++ ".") of
|
|
{ok, Tokens, _} ->
|
|
erl_parse:parse_term(Tokens);
|
|
_Err ->
|
|
{error, _Err}
|
|
end.
|
|
|
|
do_make_files(Worker, Fs, Opts) ->
|
|
%io:format("worker:~p~nfs:~p~nopts:~p~n", [Worker, Fs, Opts]),
|
|
process(Fs, Worker, lists:member(noexec, Opts), load_opt(Opts)).
|
|
|
|
|
|
%%% 读取给定的 Emakefile 并返回一个元组列表: [{Mods,Opts}]
|
|
%%% %%% Mods 是模块名称(字符串)的列表
|
|
%%% %%% Opts 是编译 Mods 时要使用的选项列表
|
|
readEMakefile(EMakefile, Opts) ->
|
|
case file:consult(EMakefile) of
|
|
{ok, EMake} ->
|
|
transform(EMake, Opts, [], []);
|
|
{error, enoent} ->
|
|
%% 没有EMakefile 仅仅编译当前没有了下的文件 如果想要编译所有子目录下的文件 使用 filelib:wildcard("./**/*.erl")
|
|
Mods = [filename:rootname(F) || F <- filelib:wildcard("*.erl")],
|
|
[{Mods, Opts}];
|
|
{error, Other} ->
|
|
io:format("the Emakefile:~s is error:~p~n", [EMakefile, Other]),
|
|
error
|
|
end.
|
|
|
|
transform([{Mod, ModOpts} | Emake], Opts, Files, Already) ->
|
|
case expand(Mod, Already) of
|
|
[] ->
|
|
transform(Emake, Opts, Files, Already);
|
|
Mods ->
|
|
transform(Emake, Opts, [{Mods, ModOpts ++ Opts} | Files], Mods ++ Already)
|
|
end;
|
|
transform([Mod | Emake], Opts, Files, Already) ->
|
|
case expand(Mod, Already) of
|
|
[] ->
|
|
transform(Emake, Opts, Files, Already);
|
|
Mods ->
|
|
transform(Emake, Opts, [{Mods, Opts} | Files], Mods ++ Already)
|
|
end;
|
|
transform([], _Opts, Files, _Already) ->
|
|
lists:reverse(Files).
|
|
|
|
expand(Mod, Already) when is_atom(Mod) ->
|
|
expand(atom_to_list(Mod), Already);
|
|
expand(Mods, Already) when is_list(Mods), not is_integer(hd(Mods)) ->
|
|
lists:concat([expand(Mod, Already) || Mod <- Mods]);
|
|
expand(Mod, Already) ->
|
|
case lists:member($*, Mod) of
|
|
true ->
|
|
Fun = fun(F, Acc) ->
|
|
M = filename:rootname(F),
|
|
case lists:member(M, Already) of
|
|
true -> Acc;
|
|
false -> [M | Acc]
|
|
end
|
|
end,
|
|
lists:foldl(Fun, [], filelib:wildcard(Mod ++ ".erl"));
|
|
false ->
|
|
Mod2 = filename:rootname(Mod, ".erl"),
|
|
case lists:member(Mod2, Already) of
|
|
true -> [];
|
|
false -> [Mod2]
|
|
end
|
|
end.
|
|
|
|
%% Any flags that are not recognixed as make flags are passed directly
|
|
%% to the compiler.
|
|
%% So for example make:all([load,debug_info]) will make everything
|
|
%% with the debug_info flag and load it.
|
|
load_opt(Opts) ->
|
|
case lists:member(netload, Opts) of
|
|
true ->
|
|
netload;
|
|
false ->
|
|
case lists:member(load, Opts) of
|
|
true ->
|
|
load;
|
|
_ ->
|
|
noload
|
|
end
|
|
end.
|
|
|
|
%% 处理
|
|
process([{[], _Opts} | Rest], Worker, NoExec, Load) ->
|
|
process(Rest, Worker, NoExec, Load);
|
|
process([{L, Opts} | Rest], Worker, NoExec, Load) ->
|
|
Len = length(L),
|
|
Worker2 = erlang:min(Len, Worker),
|
|
case catch do_worker(L, Opts, NoExec, Load, Worker2) of
|
|
error ->
|
|
error;
|
|
ok ->
|
|
process(Rest, Worker, NoExec, Load)
|
|
end;
|
|
process([], _Worker, _NoExec, _Load) ->
|
|
up_to_date.
|
|
|
|
do_worker(L, Opts, NoExec, Load, Worker) ->
|
|
|
|
%% 先开启worker个编译进程
|
|
SplitNum = min(length(L), Worker),
|
|
{L1, L2} = lists:split(SplitNum, L),
|
|
|
|
% 启动进程
|
|
Ref = make_ref(),
|
|
Pids =
|
|
[begin
|
|
start_worker([E], Opts, NoExec, Load, self(), Ref)
|
|
end || E <- L1],
|
|
do_wait_worker(length(Pids), L2, Opts, NoExec, Load, Ref).
|
|
|
|
%% @doc 一个文件编译完成后,立即进行下一个文件编译,不用等待
|
|
do_wait_worker(0, [], _Opts, _NoExec, _Load, _Ref) ->
|
|
ok;
|
|
do_wait_worker(N, L, Opts, NoExec, Load, Ref) ->
|
|
receive
|
|
{ack, Ref} ->
|
|
case L of
|
|
[H | T] ->
|
|
%% 未编译完成,编译后面的文件
|
|
start_worker(H, Opts, NoExec, Load, self(), Ref),
|
|
do_wait_worker(N, T, Opts, NoExec, Load, Ref);
|
|
[] ->
|
|
%% 等待所有结果返回
|
|
do_wait_worker(N - 1, [], Opts, NoExec, Load, Ref)
|
|
end;
|
|
{error, Ref} ->
|
|
throw(error);
|
|
{'EXIT', _P, _Reason} ->
|
|
do_wait_worker(N, L, Opts, NoExec, Load, Ref);
|
|
_Other ->
|
|
io:format("receive unknown msg:~p~n", [_Other]),
|
|
do_wait_worker(N, L, Opts, NoExec, Load, Ref)
|
|
end.
|
|
|
|
start_worker(F, Opts, NoExec, Load, Parent, Ref) ->
|
|
Fun =
|
|
fun() ->
|
|
case recompilep(coerce_2_list(F), NoExec, Load, Opts) of
|
|
error ->
|
|
Parent ! {error, Ref},
|
|
exit(error);
|
|
_ ->
|
|
ok
|
|
end,
|
|
Parent ! {ack, Ref}
|
|
end,
|
|
spawn_link(Fun).
|
|
|
|
recompilep(File, NoExec, Load, Opts) ->
|
|
ObjName = lists:append(filename:basename(File), code:objfile_extension()),
|
|
ObjFile = case lists:keysearch(outdir, 1, Opts) of
|
|
{value, {outdir, OutDir}} ->
|
|
filename:join(coerce_2_list(OutDir), ObjName);
|
|
false ->
|
|
ObjName
|
|
end,
|
|
case exists(ObjFile) of
|
|
true ->
|
|
recompilep1(File, NoExec, Load, Opts, ObjFile);
|
|
false ->
|
|
recompile(File, NoExec, Load, Opts)
|
|
end.
|
|
|
|
recompilep1(File, NoExec, Load, Opts, ObjFile) ->
|
|
{ok, Erl} = file:read_file_info(lists:append(File, ".erl")),
|
|
{ok, Obj} = file:read_file_info(ObjFile),
|
|
recompilep1(Erl, Obj, File, NoExec, Load, Opts).
|
|
|
|
recompilep1(#file_info{mtime = Te},
|
|
#file_info{mtime = To}, File, NoExec, Load, Opts) when Te > To ->
|
|
recompile(File, NoExec, Load, Opts);
|
|
recompilep1(_Erl, #file_info{mtime = To}, File, NoExec, Load, Opts) ->
|
|
recompile2(To, File, NoExec, Load, Opts).
|
|
|
|
%% recompile2(ObjMTime, File, NoExec, Load, Opts)
|
|
%% Check if file is of a later date than include files.
|
|
recompile2(ObjMTime, File, NoExec, Load, Opts) ->
|
|
IncludePath = include_opt(Opts),
|
|
case check_includes(lists:append(File, ".erl"), IncludePath, ObjMTime) of
|
|
true ->
|
|
recompile(File, NoExec, Load, Opts);
|
|
false ->
|
|
false
|
|
end.
|
|
|
|
include_opt([{i, Path} | Rest]) ->
|
|
[Path | include_opt(Rest)];
|
|
include_opt([_First | Rest]) ->
|
|
include_opt(Rest);
|
|
include_opt([]) ->
|
|
[].
|
|
|
|
%% recompile(File, NoExec, Load, Opts)
|
|
%% Actually recompile and load the file, depending on the flags.
|
|
%% Where load can be netload | load | noload
|
|
|
|
recompile(File, true, _Load, _Opts) ->
|
|
io:format("Out of date: ~s\n", [File]);
|
|
recompile(File, false, noload, Opts) ->
|
|
io:format("Recompile: ~s\n", [File]),
|
|
compile:file(File, [report_errors, report_warnings, error_summary | Opts]);
|
|
recompile(File, false, load, Opts) ->
|
|
io:format("Recompile: ~s\n", [File]),
|
|
c:c(File, Opts);
|
|
recompile(File, false, netload, Opts) ->
|
|
io:format("Recompile: ~s\n", [File]),
|
|
c:nc(File, Opts).
|
|
|
|
exists(File) ->
|
|
case file:read_file_info(File) of
|
|
{ok, _} ->
|
|
true;
|
|
_ ->
|
|
false
|
|
end.
|
|
|
|
coerce_2_list(X) when is_atom(X) ->
|
|
atom_to_list(X);
|
|
coerce_2_list(X) ->
|
|
X.
|
|
|
|
%%% If you an include file is found with a modification
|
|
%%% time larger than the modification time of the object
|
|
%%% file, return true. Otherwise return false.
|
|
check_includes(File, IncludePath, ObjMTime) ->
|
|
Path = [filename:dirname(File) | IncludePath],
|
|
case epp:open(File, Path, []) of
|
|
{ok, Epp} ->
|
|
check_includes2(Epp, File, ObjMTime);
|
|
_Error ->
|
|
false
|
|
end.
|
|
|
|
check_includes2(Epp, File, ObjMTime) ->
|
|
case epp:parse_erl_form(Epp) of
|
|
{ok, {attribute, 1, file, {File, 1}}} ->
|
|
check_includes2(Epp, File, ObjMTime);
|
|
{ok, {attribute, 1, file, {IncFile, 1}}} ->
|
|
case file:read_file_info(IncFile) of
|
|
{ok, #file_info{mtime = MTime}} when MTime > ObjMTime ->
|
|
epp:close(Epp),
|
|
true;
|
|
_ ->
|
|
check_includes2(Epp, File, ObjMTime)
|
|
end;
|
|
{ok, _} ->
|
|
check_includes2(Epp, File, ObjMTime);
|
|
{eof, _} ->
|
|
epp:close(Epp),
|
|
false;
|
|
{error, _Error} ->
|
|
check_includes2(Epp, File, ObjMTime)
|
|
end.
|