基于Emakefile的多进程编译器
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

408 lines
12 KiB

  1. -module(eMake).
  2. %% 多进程编译,修改自otp/lib/tools/src/make.erl
  3. %% 解析Emakefile,根据获取{mods, options}列表,
  4. %% 按照次序编译每项(解决编译顺序的问题)
  5. %% 其中mods也可以包含多个模块,当大于1个时,
  6. %% 可以启动多个process进行编译,从而提高编译速度.
  7. -include_lib("kernel/include/file.hrl").
  8. -export([
  9. all/1,
  10. all/2,
  11. files/2,
  12. files/3
  13. ]).
  14. -define(MakeOpts, [noexec, load, netload, noload]).
  15. all(Worker) when is_integer(Worker) ->
  16. all(Worker, []).
  17. all(Worker, Options) when is_integer(Worker) ->
  18. {MakeOpts, CompileOpts} = sort_options(Options, [], []),
  19. case read_emakefile('Emakefile', CompileOpts) of
  20. Files when is_list(Files) ->
  21. do_make_files(Worker, Files, MakeOpts);
  22. error ->
  23. error
  24. end.
  25. files(Worker, Fs) ->
  26. files(Worker, Fs, []).
  27. files(Worker, Fs0, Options) ->
  28. Fs = [filename:rootname(F, ".erl") || F <- Fs0],
  29. {MakeOpts, CompileOpts} = sort_options(Options, [], []),
  30. case get_opts_from_emakefile(Fs, 'Emakefile', CompileOpts) of
  31. Files when is_list(Files) ->
  32. do_make_files(Worker, Files, MakeOpts);
  33. error -> error
  34. end.
  35. do_make_files(Worker, Fs, Opts) ->
  36. %io:format("worker:~p~nfs:~p~nopts:~p~n", [Worker, Fs, Opts]),
  37. process(Fs, Worker, lists:member(noexec, Opts), load_opt(Opts)).
  38. sort_options([H | T], Make, Comp) ->
  39. case lists:member(H, ?MakeOpts) of
  40. true ->
  41. sort_options(T, [H | Make], Comp);
  42. false ->
  43. sort_options(T, Make, [H | Comp])
  44. end;
  45. sort_options([], Make, Comp) ->
  46. {Make, lists:reverse(Comp)}.
  47. %%% Reads the given Emakefile and returns a list of tuples: {Mods,Opts}
  48. %%% Mods is a list of module names (strings)
  49. %%% Opts is a list of options to be used when compiling Mods
  50. %%%
  51. %%% Emakefile can contain elements like this:
  52. %%% Mod.
  53. %%% {Mod,Opts}.
  54. %%% Mod is a module name which might include '*' as wildcard
  55. %%% or a list of such module names
  56. %%%
  57. %%% These elements are converted to [{ModList,OptList},...]
  58. %%% ModList is a list of modulenames (strings)
  59. read_emakefile(Emakefile, Opts) ->
  60. case file:consult(Emakefile) of
  61. {ok, Emake} ->
  62. transform(Emake, Opts, [], []);
  63. {error, enoent} ->
  64. %% No Emakefile found - return all modules in current
  65. %% directory and the options given at command line
  66. Mods = [filename:rootname(F) || F <- filelib:wildcard("*.erl")],
  67. [{Mods, Opts}];
  68. {error, Other} ->
  69. io:format("make: Trouble reading 'Emakefile':~n~p~n", [Other]),
  70. error
  71. end.
  72. transform([{Mod, ModOpts} | Emake], Opts, Files, Already) ->
  73. case expand(Mod, Already) of
  74. [] ->
  75. transform(Emake, Opts, Files, Already);
  76. Mods ->
  77. transform(Emake, Opts, [{Mods, ModOpts ++ Opts} | Files], Mods ++ Already)
  78. end;
  79. transform([Mod | Emake], Opts, Files, Already) ->
  80. case expand(Mod, Already) of
  81. [] ->
  82. transform(Emake, Opts, Files, Already);
  83. Mods ->
  84. transform(Emake, Opts, [{Mods, Opts} | Files], Mods ++ Already)
  85. end;
  86. transform([], _Opts, Files, _Already) ->
  87. lists:reverse(Files).
  88. expand(Mod, Already) when is_atom(Mod) ->
  89. expand(atom_to_list(Mod), Already);
  90. expand(Mods, Already) when is_list(Mods), not is_integer(hd(Mods)) ->
  91. lists:concat([expand(Mod, Already) || Mod <- Mods]);
  92. expand(Mod, Already) ->
  93. case lists:member($*, Mod) of
  94. true ->
  95. Fun = fun(F, Acc) ->
  96. M = filename:rootname(F),
  97. case lists:member(M, Already) of
  98. true -> Acc;
  99. false -> [M | Acc]
  100. end
  101. end,
  102. lists:foldl(Fun, [], filelib:wildcard(Mod ++ ".erl"));
  103. false ->
  104. Mod2 = filename:rootname(Mod, ".erl"),
  105. case lists:member(Mod2, Already) of
  106. true -> [];
  107. false -> [Mod2]
  108. end
  109. end.
  110. %%% Reads the given Emakefile to see if there are any specific compile
  111. %%% options given for the modules.
  112. get_opts_from_emakefile(Mods, Emakefile, Opts) ->
  113. case file:consult(Emakefile) of
  114. {ok, Emake} ->
  115. Modsandopts = transform(Emake, Opts, [], []),
  116. ModStrings = [coerce_2_list(M) || M <- Mods],
  117. get_opts_from_emakefile2(Modsandopts, ModStrings, Opts, []);
  118. {error, enoent} ->
  119. [{Mods, Opts}];
  120. {error, Other} ->
  121. io:format("make: Trouble reading 'Emakefile':~n~p~n", [Other]),
  122. error
  123. end.
  124. get_opts_from_emakefile2([{MakefileMods, O} | Rest], Mods, Opts, Result) ->
  125. case members(Mods, MakefileMods, [], Mods) of
  126. {[], _} ->
  127. get_opts_from_emakefile2(Rest, Mods, Opts, Result);
  128. {I, RestOfMods} ->
  129. get_opts_from_emakefile2(Rest, RestOfMods, Opts, [{I, O} | Result])
  130. end;
  131. get_opts_from_emakefile2([], [], _Opts, Result) ->
  132. Result;
  133. get_opts_from_emakefile2([], RestOfMods, Opts, Result) ->
  134. [{RestOfMods, Opts} | Result].
  135. members([H | T], MakefileMods, I, Rest) ->
  136. case lists:member(H, MakefileMods) of
  137. true ->
  138. members(T, MakefileMods, [H | I], lists:delete(H, Rest));
  139. false ->
  140. members(T, MakefileMods, I, Rest)
  141. end;
  142. members([], _MakefileMods, I, Rest) ->
  143. {I, Rest}.
  144. %% Any flags that are not recognixed as make flags are passed directly
  145. %% to the compiler.
  146. %% So for example make:all([load,debug_info]) will make everything
  147. %% with the debug_info flag and load it.
  148. load_opt(Opts) ->
  149. case lists:member(netload, Opts) of
  150. true ->
  151. netload;
  152. false ->
  153. case lists:member(load, Opts) of
  154. true ->
  155. load;
  156. _ ->
  157. noload
  158. end
  159. end.
  160. %% 处理
  161. process([{[], _Opts} | Rest], Worker, NoExec, Load) ->
  162. process(Rest, Worker, NoExec, Load);
  163. process([{L, Opts} | Rest], Worker, NoExec, Load) ->
  164. Len = length(L),
  165. Worker2 = erlang:min(Len, Worker),
  166. case catch do_worker(L, Opts, NoExec, Load, Worker2) of
  167. error ->
  168. error;
  169. ok ->
  170. process(Rest, Worker, NoExec, Load)
  171. end;
  172. process([], _Worker, _NoExec, _Load) ->
  173. up_to_date.
  174. %% worker进行编译
  175. %do_worker(L, Opts, NoExec, Load, Worker) ->
  176. % WorkerList = do_split_list(L, Worker),
  177. % io:format("worker:~p worker list(~p)~n,WorkerList:~p~n", [Worker, length(WorkerList)]),
  178. % % 启动进程
  179. % Ref = make_ref(),
  180. % Pids =
  181. % [begin
  182. % start_worker(E, Opts, NoExec, Load, self(), Ref)
  183. % end || E <- WorkerList],
  184. % do_wait_worker(length(Pids), Ref).
  185. %
  186. %%% 等待结果
  187. %do_wait_worker(0, _Ref) ->
  188. % ok;
  189. %do_wait_worker(N, Ref) ->
  190. % receive
  191. % {ack, Ref} ->
  192. % do_wait_worker(N - 1, Ref);
  193. % {error, Ref} ->
  194. % throw(error);
  195. % {'EXIT', _P, _Reason} ->
  196. % do_wait_worker(N, Ref);
  197. % _Other ->
  198. % io:format("receive unknown msg:~p~n", [_Other]),
  199. % do_wait_worker(N, Ref)
  200. % end.
  201. do_worker(L, Opts, NoExec, Load, Worker) ->
  202. %% 先开启worker个编译进程
  203. SplitNum = min(length(L), Worker),
  204. {L1, L2} = lists:split(SplitNum, L),
  205. % 启动进程
  206. Ref = make_ref(),
  207. Pids =
  208. [begin
  209. start_worker([E], Opts, NoExec, Load, self(), Ref)
  210. end || E <- L1],
  211. do_wait_worker(length(Pids), L2, Opts, NoExec, Load, Ref).
  212. %% @doc 一个文件编译完成后,立即进行下一个文件编译,不用等待
  213. do_wait_worker(0, [], _Opts, _NoExec, _Load, _Ref) ->
  214. ok;
  215. do_wait_worker(N, L, Opts, NoExec, Load, Ref) ->
  216. receive
  217. {ack, Ref} ->
  218. case L of
  219. [H | T] ->
  220. %% 未编译完成,编译后面的文件
  221. start_worker(H, Opts, NoExec, Load, self(), Ref),
  222. do_wait_worker(N, T, Opts, NoExec, Load, Ref);
  223. [] ->
  224. %% 等待所有结果返回
  225. do_wait_worker(N - 1, [], Opts, NoExec, Load, Ref)
  226. end;
  227. {error, Ref} ->
  228. throw(error);
  229. {'EXIT', _P, _Reason} ->
  230. do_wait_worker(N, L, Opts, NoExec, Load, Ref);
  231. _Other ->
  232. io:format("receive unknown msg:~p~n", [_Other]),
  233. do_wait_worker(N, L, Opts, NoExec, Load, Ref)
  234. end.
  235. %% 将L分割成最多包含N个子列表的列表
  236. %do_split_list(L, N) ->
  237. % Len = length(L),
  238. % % 每个列表的元素数
  239. % LLen = (Len + N - 1) div N,
  240. % do_split_list(L, LLen, []).
  241. %
  242. %do_split_list([], _N, Acc) ->
  243. % lists:reverse(Acc);
  244. %do_split_list(L, N, Acc) ->
  245. % {L2, L3} = lists:split(erlang:min(length(L), N), L),
  246. % do_split_list(L3, N, [L2 | Acc]).
  247. %
  248. %%% 启动worker进程
  249. %start_worker(L, Opts, NoExec, Load, Parent, Ref) ->
  250. % Fun =
  251. % fun() ->
  252. % [begin
  253. % case recompilep(coerce_2_list(F), NoExec, Load, Opts) of
  254. % error ->
  255. % Parent ! {error, Ref},
  256. % exit(error);
  257. % _ ->
  258. % ok
  259. % end
  260. % end || F <- L],
  261. % Parent ! {ack, Ref}
  262. % end,
  263. % spawn_link(Fun).
  264. %% 启动worker进程
  265. start_worker(F, Opts, NoExec, Load, Parent, Ref) ->
  266. Fun =
  267. fun() ->
  268. case recompilep(coerce_2_list(F), NoExec, Load, Opts) of
  269. error ->
  270. Parent ! {error, Ref},
  271. exit(error);
  272. _ ->
  273. ok
  274. end,
  275. Parent ! {ack, Ref}
  276. end,
  277. spawn_link(Fun).
  278. recompilep(File, NoExec, Load, Opts) ->
  279. ObjName = lists:append(filename:basename(File),
  280. code:objfile_extension()),
  281. ObjFile = case lists:keysearch(outdir, 1, Opts) of
  282. {value, {outdir, OutDir}} ->
  283. filename:join(coerce_2_list(OutDir), ObjName);
  284. false ->
  285. ObjName
  286. end,
  287. case exists(ObjFile) of
  288. true ->
  289. recompilep1(File, NoExec, Load, Opts, ObjFile);
  290. false ->
  291. recompile(File, NoExec, Load, Opts)
  292. end.
  293. recompilep1(File, NoExec, Load, Opts, ObjFile) ->
  294. {ok, Erl} = file:read_file_info(lists:append(File, ".erl")),
  295. {ok, Obj} = file:read_file_info(ObjFile),
  296. recompilep1(Erl, Obj, File, NoExec, Load, Opts).
  297. recompilep1(#file_info{mtime = Te},
  298. #file_info{mtime = To}, File, NoExec, Load, Opts) when Te > To ->
  299. recompile(File, NoExec, Load, Opts);
  300. recompilep1(_Erl, #file_info{mtime = To}, File, NoExec, Load, Opts) ->
  301. recompile2(To, File, NoExec, Load, Opts).
  302. %% recompile2(ObjMTime, File, NoExec, Load, Opts)
  303. %% Check if file is of a later date than include files.
  304. recompile2(ObjMTime, File, NoExec, Load, Opts) ->
  305. IncludePath = include_opt(Opts),
  306. case check_includes(lists:append(File, ".erl"), IncludePath, ObjMTime) of
  307. true ->
  308. recompile(File, NoExec, Load, Opts);
  309. false ->
  310. false
  311. end.
  312. include_opt([{i, Path} | Rest]) ->
  313. [Path | include_opt(Rest)];
  314. include_opt([_First | Rest]) ->
  315. include_opt(Rest);
  316. include_opt([]) ->
  317. [].
  318. %% recompile(File, NoExec, Load, Opts)
  319. %% Actually recompile and load the file, depending on the flags.
  320. %% Where load can be netload | load | noload
  321. recompile(File, true, _Load, _Opts) ->
  322. io:format("Out of date: ~s\n", [File]);
  323. recompile(File, false, noload, Opts) ->
  324. io:format("Recompile: ~s\n", [File]),
  325. compile:file(File, [report_errors, report_warnings, error_summary | Opts]);
  326. recompile(File, false, load, Opts) ->
  327. io:format("Recompile: ~s\n", [File]),
  328. c:c(File, Opts);
  329. recompile(File, false, netload, Opts) ->
  330. io:format("Recompile: ~s\n", [File]),
  331. c:nc(File, Opts).
  332. exists(File) ->
  333. case file:read_file_info(File) of
  334. {ok, _} ->
  335. true;
  336. _ ->
  337. false
  338. end.
  339. coerce_2_list(X) when is_atom(X) ->
  340. atom_to_list(X);
  341. coerce_2_list(X) ->
  342. X.
  343. %%% If you an include file is found with a modification
  344. %%% time larger than the modification time of the object
  345. %%% file, return true. Otherwise return false.
  346. check_includes(File, IncludePath, ObjMTime) ->
  347. Path = [filename:dirname(File) | IncludePath],
  348. case epp:open(File, Path, []) of
  349. {ok, Epp} ->
  350. check_includes2(Epp, File, ObjMTime);
  351. _Error ->
  352. false
  353. end.
  354. check_includes2(Epp, File, ObjMTime) ->
  355. case epp:parse_erl_form(Epp) of
  356. {ok, {attribute, 1, file, {File, 1}}} ->
  357. check_includes2(Epp, File, ObjMTime);
  358. {ok, {attribute, 1, file, {IncFile, 1}}} ->
  359. case file:read_file_info(IncFile) of
  360. {ok, #file_info{mtime = MTime}} when MTime > ObjMTime ->
  361. epp:close(Epp),
  362. true;
  363. _ ->
  364. check_includes2(Epp, File, ObjMTime)
  365. end;
  366. {ok, _} ->
  367. check_includes2(Epp, File, ObjMTime);
  368. {eof, _} ->
  369. epp:close(Epp),
  370. false;
  371. {error, _Error} ->
  372. check_includes2(Epp, File, ObjMTime)
  373. end.