基于Emakefile的多进程编译器
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

315 行
9.6 KiB

3 年前
3 年前
3 年前
3 年前
3 年前
3 年前
3 年前
3 年前
3 年前
  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. main/1
  10. , all/3
  11. ]).
  12. -define(MakeOpts, [noexec, load, netload, noload]).
  13. main(Args) ->
  14. io:format("~p~n", [Args]),
  15. case Args of
  16. [] ->
  17. all(max(1, erlang:system_info(schedulers) - 1), "./Emakefile", []);
  18. [EMakeFileOrCnt] ->
  19. try list_to_integer(EMakeFileOrCnt) of
  20. Cnt ->
  21. all(max(1, Cnt), "./Emakefile", [])
  22. catch _:_ ->
  23. all(max(1, erlang:system_info(schedulers) - 1), EMakeFileOrCnt, [])
  24. end;
  25. [EMakeFile, CntStr] ->
  26. all(max(1, list_to_integer(CntStr)), EMakeFile, []);
  27. [EMakeFile, CntStr, OptsStr] ->
  28. {ok, Opts} = strToTerm(OptsStr),
  29. all(max(1, list_to_integer(CntStr)), EMakeFile, Opts)
  30. end.
  31. all(Worker, EMakeFile, Opts) ->
  32. {MakeOpts, CompileOpts} = splitOpts(Opts, [], []),
  33. case readEMakefile(EMakeFile, CompileOpts) of
  34. Files when is_list(Files) ->
  35. do_make_files(Worker, Files, MakeOpts);
  36. error ->
  37. error
  38. end.
  39. splitOpts([], Make, Compile) ->
  40. {Make, lists:reverse(Compile)};
  41. splitOpts([H | T], Make, Compile) ->
  42. case lists:member(H, ?MakeOpts) of
  43. true ->
  44. splitOpts(T, [H | Make], Compile);
  45. false ->
  46. splitOpts(T, Make, [H | Compile])
  47. end.
  48. %% term反序列化, string转换为term
  49. strToTerm(String) ->
  50. case erl_scan:string(String ++ ".") of
  51. {ok, Tokens, _} ->
  52. erl_parse:parse_term(Tokens);
  53. _Err ->
  54. {error, _Err}
  55. end.
  56. do_make_files(Worker, Fs, Opts) ->
  57. %io:format("worker:~p~nfs:~p~nopts:~p~n", [Worker, Fs, Opts]),
  58. process(Fs, Worker, lists:member(noexec, Opts), load_opt(Opts)).
  59. %%% 读取给定的 Emakefile 并返回一个元组列表: [{Mods,Opts}]
  60. %%% %%% Mods 是模块名称(字符串)的列表
  61. %%% %%% Opts 是编译 Mods 时要使用的选项列表
  62. readEMakefile(EMakefile, Opts) ->
  63. case file:consult(EMakefile) of
  64. {ok, EMake} ->
  65. transform(EMake, Opts, [], []);
  66. {error, enoent} ->
  67. %% 没有EMakefile 仅仅编译当前没有了下的文件 如果想要编译所有子目录下的文件 使用 filelib:wildcard("./**/*.erl")
  68. Mods = [filename:rootname(F) || F <- filelib:wildcard("*.erl")],
  69. [{Mods, Opts}];
  70. {error, Other} ->
  71. io:format("the Emakefile:~s is error:~p~n", [EMakefile, Other]),
  72. error
  73. end.
  74. transform([{Mod, ModOpts} | Emake], Opts, Files, Already) ->
  75. case expand(Mod, Already) of
  76. [] ->
  77. transform(Emake, Opts, Files, Already);
  78. Mods ->
  79. transform(Emake, Opts, [{Mods, ModOpts ++ Opts} | Files], Mods ++ Already)
  80. end;
  81. transform([Mod | Emake], Opts, Files, Already) ->
  82. case expand(Mod, Already) of
  83. [] ->
  84. transform(Emake, Opts, Files, Already);
  85. Mods ->
  86. transform(Emake, Opts, [{Mods, Opts} | Files], Mods ++ Already)
  87. end;
  88. transform([], _Opts, Files, _Already) ->
  89. lists:reverse(Files).
  90. expand(Mod, Already) when is_atom(Mod) ->
  91. expand(atom_to_list(Mod), Already);
  92. expand(Mods, Already) when is_list(Mods), not is_integer(hd(Mods)) ->
  93. lists:concat([expand(Mod, Already) || Mod <- Mods]);
  94. expand(Mod, Already) ->
  95. case lists:member($*, Mod) of
  96. true ->
  97. Fun = fun(F, Acc) ->
  98. M = filename:rootname(F),
  99. case lists:member(M, Already) of
  100. true -> Acc;
  101. false -> [M | Acc]
  102. end
  103. end,
  104. lists:foldl(Fun, [], filelib:wildcard(Mod ++ ".erl"));
  105. false ->
  106. Mod2 = filename:rootname(Mod, ".erl"),
  107. case lists:member(Mod2, Already) of
  108. true -> [];
  109. false -> [Mod2]
  110. end
  111. end.
  112. %% Any flags that are not recognixed as make flags are passed directly
  113. %% to the compiler.
  114. %% So for example make:all([load,debug_info]) will make everything
  115. %% with the debug_info flag and load it.
  116. load_opt(Opts) ->
  117. case lists:member(netload, Opts) of
  118. true ->
  119. netload;
  120. false ->
  121. case lists:member(load, Opts) of
  122. true ->
  123. load;
  124. _ ->
  125. noload
  126. end
  127. end.
  128. %% 处理
  129. process([{[], _Opts} | Rest], Worker, NoExec, Load) ->
  130. process(Rest, Worker, NoExec, Load);
  131. process([{L, Opts} | Rest], Worker, NoExec, Load) ->
  132. Len = length(L),
  133. Worker2 = erlang:min(Len, Worker),
  134. case catch do_worker(L, Opts, NoExec, Load, Worker2) of
  135. error ->
  136. error;
  137. ok ->
  138. process(Rest, Worker, NoExec, Load)
  139. end;
  140. process([], _Worker, _NoExec, _Load) ->
  141. up_to_date.
  142. do_worker(L, Opts, NoExec, Load, Worker) ->
  143. %% 先开启worker个编译进程
  144. SplitNum = min(length(L), Worker),
  145. {L1, L2} = lists:split(SplitNum, L),
  146. % 启动进程
  147. Ref = make_ref(),
  148. Pids =
  149. [begin
  150. start_worker([E], Opts, NoExec, Load, self(), Ref)
  151. end || E <- L1],
  152. do_wait_worker(length(Pids), L2, Opts, NoExec, Load, Ref).
  153. %% @doc 一个文件编译完成后,立即进行下一个文件编译,不用等待
  154. do_wait_worker(0, [], _Opts, _NoExec, _Load, _Ref) ->
  155. ok;
  156. do_wait_worker(N, L, Opts, NoExec, Load, Ref) ->
  157. receive
  158. {ack, Ref} ->
  159. case L of
  160. [H | T] ->
  161. %% 未编译完成,编译后面的文件
  162. start_worker(H, Opts, NoExec, Load, self(), Ref),
  163. do_wait_worker(N, T, Opts, NoExec, Load, Ref);
  164. [] ->
  165. %% 等待所有结果返回
  166. do_wait_worker(N - 1, [], Opts, NoExec, Load, Ref)
  167. end;
  168. {error, Ref} ->
  169. throw(error);
  170. {'EXIT', _P, _Reason} ->
  171. do_wait_worker(N, L, Opts, NoExec, Load, Ref);
  172. _Other ->
  173. io:format("receive unknown msg:~p~n", [_Other]),
  174. do_wait_worker(N, L, Opts, NoExec, Load, Ref)
  175. end.
  176. start_worker(F, Opts, NoExec, Load, Parent, Ref) ->
  177. Fun =
  178. fun() ->
  179. case recompilep(coerce_2_list(F), NoExec, Load, Opts) of
  180. error ->
  181. Parent ! {error, Ref},
  182. exit(error);
  183. _ ->
  184. ok
  185. end,
  186. Parent ! {ack, Ref}
  187. end,
  188. spawn_link(Fun).
  189. recompilep(File, NoExec, Load, Opts) ->
  190. ObjName = lists:append(filename:basename(File), code:objfile_extension()),
  191. ObjFile = case lists:keysearch(outdir, 1, Opts) of
  192. {value, {outdir, OutDir}} ->
  193. filename:join(coerce_2_list(OutDir), ObjName);
  194. false ->
  195. ObjName
  196. end,
  197. case exists(ObjFile) of
  198. true ->
  199. recompilep1(File, NoExec, Load, Opts, ObjFile);
  200. false ->
  201. recompile(File, NoExec, Load, Opts)
  202. end.
  203. recompilep1(File, NoExec, Load, Opts, ObjFile) ->
  204. {ok, Erl} = file:read_file_info(lists:append(File, ".erl")),
  205. {ok, Obj} = file:read_file_info(ObjFile),
  206. recompilep1(Erl, Obj, File, NoExec, Load, Opts).
  207. recompilep1(#file_info{mtime = Te},
  208. #file_info{mtime = To}, File, NoExec, Load, Opts) when Te > To ->
  209. recompile(File, NoExec, Load, Opts);
  210. recompilep1(_Erl, #file_info{mtime = To}, File, NoExec, Load, Opts) ->
  211. recompile2(To, File, NoExec, Load, Opts).
  212. %% recompile2(ObjMTime, File, NoExec, Load, Opts)
  213. %% Check if file is of a later date than include files.
  214. recompile2(ObjMTime, File, NoExec, Load, Opts) ->
  215. IncludePath = include_opt(Opts),
  216. case check_includes(lists:append(File, ".erl"), IncludePath, ObjMTime) of
  217. true ->
  218. recompile(File, NoExec, Load, Opts);
  219. false ->
  220. false
  221. end.
  222. include_opt([{i, Path} | Rest]) ->
  223. [Path | include_opt(Rest)];
  224. include_opt([_First | Rest]) ->
  225. include_opt(Rest);
  226. include_opt([]) ->
  227. [].
  228. %% recompile(File, NoExec, Load, Opts)
  229. %% Actually recompile and load the file, depending on the flags.
  230. %% Where load can be netload | load | noload
  231. recompile(File, true, _Load, _Opts) ->
  232. io:format("Out of date: ~s\n", [File]);
  233. recompile(File, false, noload, Opts) ->
  234. io:format("Recompile: ~s\n", [File]),
  235. compile:file(File, [report_errors, report_warnings, error_summary | Opts]);
  236. recompile(File, false, load, Opts) ->
  237. io:format("Recompile: ~s\n", [File]),
  238. c:c(File, Opts);
  239. recompile(File, false, netload, Opts) ->
  240. io:format("Recompile: ~s\n", [File]),
  241. c:nc(File, Opts).
  242. exists(File) ->
  243. case file:read_file_info(File) of
  244. {ok, _} ->
  245. true;
  246. _ ->
  247. false
  248. end.
  249. coerce_2_list(X) when is_atom(X) ->
  250. atom_to_list(X);
  251. coerce_2_list(X) ->
  252. X.
  253. %%% If you an include file is found with a modification
  254. %%% time larger than the modification time of the object
  255. %%% file, return true. Otherwise return false.
  256. check_includes(File, IncludePath, ObjMTime) ->
  257. Path = [filename:dirname(File) | IncludePath],
  258. case epp:open(File, Path, []) of
  259. {ok, Epp} ->
  260. check_includes2(Epp, File, ObjMTime);
  261. _Error ->
  262. false
  263. end.
  264. check_includes2(Epp, File, ObjMTime) ->
  265. case epp:parse_erl_form(Epp) of
  266. {ok, {attribute, 1, file, {File, 1}}} ->
  267. check_includes2(Epp, File, ObjMTime);
  268. {ok, {attribute, 1, file, {IncFile, 1}}} ->
  269. case file:read_file_info(IncFile) of
  270. {ok, #file_info{mtime = MTime}} when MTime > ObjMTime ->
  271. epp:close(Epp),
  272. true;
  273. _ ->
  274. check_includes2(Epp, File, ObjMTime)
  275. end;
  276. {ok, _} ->
  277. check_includes2(Epp, File, ObjMTime);
  278. {eof, _} ->
  279. epp:close(Epp),
  280. false;
  281. {error, _Error} ->
  282. check_includes2(Epp, File, ObjMTime)
  283. end.