|
|
@ -1,408 +0,0 @@ |
|
|
|
%% 多进程编译,修改自otp/lib/tools/src/make.erl |
|
|
|
%% 解析Emakefile,根据获取{mods, options}列表, |
|
|
|
%% 按照次序编译每项(解决编译顺序的问题) |
|
|
|
%% 其中mods也可以包含多个模块,当大于1个时, |
|
|
|
%% 可以启动多个process进行编译,从而提高编译速度. |
|
|
|
|
|
|
|
-module(utMMake). |
|
|
|
-include_lib("kernel/include/file.hrl"). |
|
|
|
|
|
|
|
-export([ |
|
|
|
all/1, |
|
|
|
all/2, |
|
|
|
files/2, |
|
|
|
files/3 |
|
|
|
]). |
|
|
|
|
|
|
|
-define(MakeOpts, [noexec, load, netload, noload]). |
|
|
|
|
|
|
|
all(Worker) when is_integer(Worker) -> |
|
|
|
all(Worker, []). |
|
|
|
|
|
|
|
all(Worker, Options) when is_integer(Worker) -> |
|
|
|
{MakeOpts, CompileOpts} = sort_options(Options, [], []), |
|
|
|
case read_emakefile('Emakefile', CompileOpts) of |
|
|
|
Files when is_list(Files) -> |
|
|
|
do_make_files(Worker, Files, MakeOpts); |
|
|
|
error -> |
|
|
|
error |
|
|
|
end. |
|
|
|
|
|
|
|
files(Worker, Fs) -> |
|
|
|
files(Worker, Fs, []). |
|
|
|
|
|
|
|
files(Worker, Fs0, Options) -> |
|
|
|
Fs = [filename:rootname(F, ".erl") || F <- Fs0], |
|
|
|
{MakeOpts, CompileOpts} = sort_options(Options, [], []), |
|
|
|
case get_opts_from_emakefile(Fs, 'Emakefile', CompileOpts) of |
|
|
|
Files when is_list(Files) -> |
|
|
|
do_make_files(Worker, Files, MakeOpts); |
|
|
|
error -> error |
|
|
|
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)). |
|
|
|
|
|
|
|
sort_options([H | T], Make, Comp) -> |
|
|
|
case lists:member(H, ?MakeOpts) of |
|
|
|
true -> |
|
|
|
sort_options(T, [H | Make], Comp); |
|
|
|
false -> |
|
|
|
sort_options(T, Make, [H | Comp]) |
|
|
|
end; |
|
|
|
sort_options([], Make, Comp) -> |
|
|
|
{Make, lists:reverse(Comp)}. |
|
|
|
|
|
|
|
%%% Reads the given Emakefile and returns a list of tuples: {Mods,Opts} |
|
|
|
%%% Mods is a list of module names (strings) |
|
|
|
%%% Opts is a list of options to be used when compiling Mods |
|
|
|
%%% |
|
|
|
%%% Emakefile can contain elements like this: |
|
|
|
%%% Mod. |
|
|
|
%%% {Mod,Opts}. |
|
|
|
%%% Mod is a module name which might include '*' as wildcard |
|
|
|
%%% or a list of such module names |
|
|
|
%%% |
|
|
|
%%% These elements are converted to [{ModList,OptList},...] |
|
|
|
%%% ModList is a list of modulenames (strings) |
|
|
|
read_emakefile(Emakefile, Opts) -> |
|
|
|
case file:consult(Emakefile) of |
|
|
|
{ok, Emake} -> |
|
|
|
transform(Emake, Opts, [], []); |
|
|
|
{error, enoent} -> |
|
|
|
%% No Emakefile found - return all modules in current |
|
|
|
%% directory and the options given at command line |
|
|
|
Mods = [filename:rootname(F) || F <- filelib:wildcard("*.erl")], |
|
|
|
[{Mods, Opts}]; |
|
|
|
{error, Other} -> |
|
|
|
io:format("make: Trouble reading 'Emakefile':~n~p~n", [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. |
|
|
|
|
|
|
|
%%% Reads the given Emakefile to see if there are any specific compile |
|
|
|
%%% options given for the modules. |
|
|
|
get_opts_from_emakefile(Mods, Emakefile, Opts) -> |
|
|
|
case file:consult(Emakefile) of |
|
|
|
{ok, Emake} -> |
|
|
|
Modsandopts = transform(Emake, Opts, [], []), |
|
|
|
ModStrings = [coerce_2_list(M) || M <- Mods], |
|
|
|
get_opts_from_emakefile2(Modsandopts, ModStrings, Opts, []); |
|
|
|
{error, enoent} -> |
|
|
|
[{Mods, Opts}]; |
|
|
|
{error, Other} -> |
|
|
|
io:format("make: Trouble reading 'Emakefile':~n~p~n", [Other]), |
|
|
|
error |
|
|
|
end. |
|
|
|
|
|
|
|
get_opts_from_emakefile2([{MakefileMods, O} | Rest], Mods, Opts, Result) -> |
|
|
|
case members(Mods, MakefileMods, [], Mods) of |
|
|
|
{[], _} -> |
|
|
|
get_opts_from_emakefile2(Rest, Mods, Opts, Result); |
|
|
|
{I, RestOfMods} -> |
|
|
|
get_opts_from_emakefile2(Rest, RestOfMods, Opts, [{I, O} | Result]) |
|
|
|
end; |
|
|
|
get_opts_from_emakefile2([], [], _Opts, Result) -> |
|
|
|
Result; |
|
|
|
get_opts_from_emakefile2([], RestOfMods, Opts, Result) -> |
|
|
|
[{RestOfMods, Opts} | Result]. |
|
|
|
|
|
|
|
members([H | T], MakefileMods, I, Rest) -> |
|
|
|
case lists:member(H, MakefileMods) of |
|
|
|
true -> |
|
|
|
members(T, MakefileMods, [H | I], lists:delete(H, Rest)); |
|
|
|
false -> |
|
|
|
members(T, MakefileMods, I, Rest) |
|
|
|
end; |
|
|
|
members([], _MakefileMods, I, Rest) -> |
|
|
|
{I, Rest}. |
|
|
|
|
|
|
|
|
|
|
|
%% 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. |
|
|
|
|
|
|
|
%% worker进行编译 |
|
|
|
%do_worker(L, Opts, NoExec, Load, Worker) -> |
|
|
|
% WorkerList = do_split_list(L, Worker), |
|
|
|
% io:format("worker:~p worker list(~p)~n,WorkerList:~p~n", [Worker, length(WorkerList)]), |
|
|
|
% % 启动进程 |
|
|
|
% Ref = make_ref(), |
|
|
|
% Pids = |
|
|
|
% [begin |
|
|
|
% start_worker(E, Opts, NoExec, Load, self(), Ref) |
|
|
|
% end || E <- WorkerList], |
|
|
|
% do_wait_worker(length(Pids), Ref). |
|
|
|
% |
|
|
|
%%% 等待结果 |
|
|
|
%do_wait_worker(0, _Ref) -> |
|
|
|
% ok; |
|
|
|
%do_wait_worker(N, Ref) -> |
|
|
|
% receive |
|
|
|
% {ack, Ref} -> |
|
|
|
% do_wait_worker(N - 1, Ref); |
|
|
|
% {error, Ref} -> |
|
|
|
% throw(error); |
|
|
|
% {'EXIT', _P, _Reason} -> |
|
|
|
% do_wait_worker(N, Ref); |
|
|
|
% _Other -> |
|
|
|
% io:format("receive unknown msg:~p~n", [_Other]), |
|
|
|
% do_wait_worker(N, Ref) |
|
|
|
% end. |
|
|
|
|
|
|
|
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. |
|
|
|
|
|
|
|
%% 将L分割成最多包含N个子列表的列表 |
|
|
|
%do_split_list(L, N) -> |
|
|
|
% Len = length(L), |
|
|
|
% % 每个列表的元素数 |
|
|
|
% LLen = (Len + N - 1) div N, |
|
|
|
% do_split_list(L, LLen, []). |
|
|
|
% |
|
|
|
%do_split_list([], _N, Acc) -> |
|
|
|
% lists:reverse(Acc); |
|
|
|
%do_split_list(L, N, Acc) -> |
|
|
|
% {L2, L3} = lists:split(erlang:min(length(L), N), L), |
|
|
|
% do_split_list(L3, N, [L2 | Acc]). |
|
|
|
% |
|
|
|
%%% 启动worker进程 |
|
|
|
%start_worker(L, Opts, NoExec, Load, Parent, Ref) -> |
|
|
|
% Fun = |
|
|
|
% fun() -> |
|
|
|
% [begin |
|
|
|
% case recompilep(coerce_2_list(F), NoExec, Load, Opts) of |
|
|
|
% error -> |
|
|
|
% Parent ! {error, Ref}, |
|
|
|
% exit(error); |
|
|
|
% _ -> |
|
|
|
% ok |
|
|
|
% end |
|
|
|
% end || F <- L], |
|
|
|
% Parent ! {ack, Ref} |
|
|
|
% end, |
|
|
|
% spawn_link(Fun). |
|
|
|
%% 启动worker进程 |
|
|
|
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. |