|
|
- -module(eMake).
- %% 多进程编译,修改自otp/lib/tools/src/make.erl
- %% 解析Emakefile,根据获取{mods, options}列表,
- %% 按照次序编译每项(解决编译顺序的问题)
- %% 其中mods也可以包含多个模块,当大于1个时,
- %% 可以启动多个process进行编译,从而提高编译速度.
-
- -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.
|