aboutsummaryrefslogtreecommitdiffstats
path: root/lib/common_test/src/ct_make.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common_test/src/ct_make.erl')
-rw-r--r--lib/common_test/src/ct_make.erl344
1 files changed, 344 insertions, 0 deletions
diff --git a/lib/common_test/src/ct_make.erl b/lib/common_test/src/ct_make.erl
new file mode 100644
index 0000000000..233e45248e
--- /dev/null
+++ b/lib/common_test/src/ct_make.erl
@@ -0,0 +1,344 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% Purpose : Basic make facility for Common Test
+%%
+%% Compares date stamps of .erl and Object files - recompiles when
+%% necessary.
+%% Files to be checked are contained in a file 'Emakefile'
+%% If Emakefile is missing the current directory is used.
+%%
+
+-module(ct_make).
+
+-export([all/0,all/1,files/1,files/2]).
+
+-include_lib("kernel/include/file.hrl").
+
+-define(MakeOpts,[noexec,load,netload,noload]).
+
+all() ->
+ all([]).
+
+all(Options) ->
+ {MakeOpts,CompileOpts} = sort_options(Options,[],[]),
+ case read_emakefile('Emakefile',CompileOpts) of
+ Files when is_list(Files) ->
+ do_make_files(Files,MakeOpts);
+ error ->
+ {error,[]}
+ end.
+
+files(Fs) ->
+ files(Fs, []).
+
+files(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(Files,MakeOpts);
+ error ->
+ {error,[]}
+ end.
+
+do_make_files(Fs, Opts) ->
+ process(Fs, 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], NoExec, Load, Result) ->
+ process(Rest, NoExec, Load, Result);
+process([{[H|T],Opts}|Rest], NoExec, Load, Result) ->
+ case recompilep(coerce_2_list(H), NoExec, Load, Opts) of
+ error ->
+ process([{T,Opts}|Rest], NoExec, Load, [{H,error}|Result]);
+ Info ->
+ process([{T,Opts}|Rest], NoExec, Load, [{H,Info}|Result])
+ end;
+process([], NoExec, _Load, Result) ->
+ if not NoExec ->
+ case lists:keysearch(error, 2, Result) of
+ {value,_} ->
+ {error,Result};
+ false ->
+ {up_to_date,Result}
+ end;
+ true ->
+ Result
+ end.
+
+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),
+ case {readable(Erl), writable(Obj)} of
+ {true, true} ->
+ recompilep1(Erl, Obj, File, NoExec, Load, Opts);
+ _ ->
+ error
+ end.
+
+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 ->
+ up_to_date
+ 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, NoExec, Load, Opts) ->
+ case do_recompile(File, NoExec, Load, Opts) of
+ {ok,_} -> ok;
+ Other -> Other
+ end.
+
+do_recompile(_File, true, _Load, _Opts) ->
+ out_of_date;
+do_recompile(File, false, noload, Opts) ->
+ io:format("Recompile: ~s\n",[File]),
+ compile:file(File, [report_errors, report_warnings, error_summary |Opts]);
+do_recompile(File, false, load, Opts) ->
+ io:format("Recompile: ~s\n",[File]),
+ c:c(File, Opts);
+do_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.
+
+readable(#file_info{access=read_write}) -> true;
+readable(#file_info{access=read}) -> true;
+readable(_) -> false.
+
+writable(#file_info{access=read_write}) -> true;
+writable(#file_info{access=write}) -> true;
+writable(_) -> false.
+
+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.