commit a537bd7b01af2c148279786d57e830a44f24f7c5 Author: lijie <1713699517@qq.com> Date: Wed Apr 20 20:40:03 2022 +0800 ft: 初始化提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ad44f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +.eunit +*.o +*.beam +*.plt +erl_crash.dump +.concrete/DEV_MODE + +# rebar 2.x +.rebar +rel/example_project +ebin/* +deps + +# rebar 3 +.rebar3 +_build/ +_checkouts/ +rebar.lock + +# idea +.idea +*.iml +cmake-build* +CMakeLists.txt + +# nif compile temp file +*.pdb +*.d +compile_commands.json \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d9494a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 AICells + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee78dc0 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +eMake +===== + + erlang 多进程编译器 + +Build +----- + + $ rebar3 escriptize -> eMake + $ rebar3 compile \ No newline at end of file diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..cdd4617 --- /dev/null +++ b/rebar.config @@ -0,0 +1,10 @@ +{erl_opts, [no_debug_info]}. +{deps,[]}. + + +%% escript +{escript_main_app, eMake}. % specify which app is the escript app +{escript_name, eMake}. % name of final generated escript +{escript_emu_args, "%%! -escript main eMake\n"}. % emulator args +%%{escript_shebang, "#!/usr/bin/env escript\n"}. % executable line +%%{escript_comment, "%%\n"}. % comment at top of escript file \ No newline at end of file diff --git a/src/eMake.app.src b/src/eMake.app.src new file mode 100644 index 0000000..d564840 --- /dev/null +++ b/src/eMake.app.src @@ -0,0 +1,11 @@ +{application, eMake, + [{description, "An OTP library"}, + {vsn, "0.1.0"}, + {registered, []}, + {applications, [kernel, stdlib]}, + {env, []}, + {modules, []}, + + {licenses, ["MIT"]}, + {links, []} + ]}. diff --git a/src/eMake.erl b/src/eMake.erl new file mode 100644 index 0000000..5f8e89b --- /dev/null +++ b/src/eMake.erl @@ -0,0 +1,408 @@ +-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.