1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2009-2017. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21%% 22%% Purpose : Basic make facility for Common Test 23%% 24%% Compares date stamps of .erl and Object files - recompiles when 25%% necessary. 26%% Files to be checked are contained in a file 'Emakefile' 27%% If Emakefile is missing the current directory is used. 28%% 29 30-module(ct_make). 31 32-export([all/0,all/1,files/1,files/2]). 33 34-include_lib("kernel/include/file.hrl"). 35 36-define(MakeOpts,[noexec,load,netload,noload]). 37 38all() -> 39 all([]). 40 41all(Options) -> 42 {MakeOpts,CompileOpts} = sort_options(Options,[],[]), 43 case read_emakefile('Emakefile',CompileOpts) of 44 Files when is_list(Files) -> 45 do_make_files(Files,MakeOpts); 46 error -> 47 {error,[]} 48 end. 49 50files(Fs) -> 51 files(Fs, []). 52 53files(Fs0, Options) -> 54 Fs = [filename:rootname(F,".erl") || F <- Fs0], 55 {MakeOpts,CompileOpts} = sort_options(Options,[],[]), 56 case get_opts_from_emakefile(Fs,'Emakefile',CompileOpts) of 57 Files when is_list(Files) -> 58 do_make_files(Files,MakeOpts); 59 error -> 60 {error,[]} 61 end. 62 63do_make_files(Fs, Opts) -> 64 process(Fs, lists:member(noexec, Opts), load_opt(Opts), []). 65 66 67sort_options([H|T],Make,Comp) -> 68 case lists:member(H,?MakeOpts) of 69 true -> 70 sort_options(T,[H|Make],Comp); 71 false -> 72 sort_options(T,Make,[H|Comp]) 73 end; 74sort_options([],Make,Comp) -> 75 {Make,lists:reverse(Comp)}. 76 77%%% Reads the given Emakefile and returns a list of tuples: {Mods,Opts} 78%%% Mods is a list of module names (strings) 79%%% Opts is a list of options to be used when compiling Mods 80%%% 81%%% Emakefile can contain elements like this: 82%%% Mod. 83%%% {Mod,Opts}. 84%%% Mod is a module name which might include '*' as wildcard 85%%% or a list of such module names 86%%% 87%%% These elements are converted to [{ModList,OptList},...] 88%%% ModList is a list of modulenames (strings) 89read_emakefile(Emakefile,Opts) -> 90 case file:consult(Emakefile) of 91 {ok,Emake} -> 92 transform(Emake,Opts,[],[]); 93 {error,enoent} -> 94 %% No Emakefile found - return all modules in current 95 %% directory and the options given at command line 96 Mods = [filename:rootname(F) || F <- filelib:wildcard("*.erl")], 97 [{Mods, Opts}]; 98 {error,Other} -> 99 io:format("make: Trouble reading 'Emakefile':~n~tp~n",[Other]), 100 error 101 end. 102 103transform([{Mod,ModOpts}|Emake],Opts,Files,Already) -> 104 case expand(Mod,Already) of 105 [] -> 106 transform(Emake,Opts,Files,Already); 107 Mods -> 108 transform(Emake,Opts,[{Mods,ModOpts++Opts}|Files],Mods++Already) 109 end; 110transform([Mod|Emake],Opts,Files,Already) -> 111 case expand(Mod,Already) of 112 [] -> 113 transform(Emake,Opts,Files,Already); 114 Mods -> 115 transform(Emake,Opts,[{Mods,Opts}|Files],Mods++Already) 116 end; 117transform([],_Opts,Files,_Already) -> 118 lists:reverse(Files). 119 120expand(Mod,Already) when is_atom(Mod) -> 121 expand(atom_to_list(Mod),Already); 122expand(Mods,Already) when is_list(Mods), not is_integer(hd(Mods)) -> 123 lists:concat([expand(Mod,Already) || Mod <- Mods]); 124expand(Mod,Already) -> 125 case lists:member($*,Mod) of 126 true -> 127 Fun = fun(F,Acc) -> 128 M = filename:rootname(F), 129 case lists:member(M,Already) of 130 true -> Acc; 131 false -> [M|Acc] 132 end 133 end, 134 lists:foldl(Fun, [], filelib:wildcard(Mod++".erl")); 135 false -> 136 Mod2 = filename:rootname(Mod, ".erl"), 137 case lists:member(Mod2,Already) of 138 true -> []; 139 false -> [Mod2] 140 end 141 end. 142 143%%% Reads the given Emakefile to see if there are any specific compile 144%%% options given for the modules. 145get_opts_from_emakefile(Mods,Emakefile,Opts) -> 146 case file:consult(Emakefile) of 147 {ok,Emake} -> 148 Modsandopts = transform(Emake,Opts,[],[]), 149 ModStrings = [coerce_2_list(M) || M <- Mods], 150 get_opts_from_emakefile2(Modsandopts,ModStrings,Opts,[]); 151 {error,enoent} -> 152 [{Mods, Opts}]; 153 {error,Other} -> 154 io:format("make: Trouble reading 'Emakefile':~n~tp~n",[Other]), 155 error 156 end. 157 158get_opts_from_emakefile2([{MakefileMods,O}|Rest],Mods,Opts,Result) -> 159 case members(Mods,MakefileMods,[],Mods) of 160 {[],_} -> 161 get_opts_from_emakefile2(Rest,Mods,Opts,Result); 162 {I,RestOfMods} -> 163 get_opts_from_emakefile2(Rest,RestOfMods,Opts,[{I,O}|Result]) 164 end; 165get_opts_from_emakefile2([],[],_Opts,Result) -> 166 Result; 167get_opts_from_emakefile2([],RestOfMods,Opts,Result) -> 168 [{RestOfMods,Opts}|Result]. 169 170members([H|T],MakefileMods,I,Rest) -> 171 case lists:member(H,MakefileMods) of 172 true -> 173 members(T,MakefileMods,[H|I],lists:delete(H,Rest)); 174 false -> 175 members(T,MakefileMods,I,Rest) 176 end; 177members([],_MakefileMods,I,Rest) -> 178 {I,Rest}. 179 180 181%% Any flags that are not recognised as make flags are passed directly 182%% to the compiler. 183%% So for example make:all([load,debug_info]) will make everything 184%% with the debug_info flag and load it. 185 186load_opt(Opts) -> 187 case lists:member(netload,Opts) of 188 true -> 189 netload; 190 false -> 191 case lists:member(load,Opts) of 192 true -> 193 load; 194 _ -> 195 noload 196 end 197 end. 198 199 200process([{[],_Opts}|Rest], NoExec, Load, Result) -> 201 process(Rest, NoExec, Load, Result); 202process([{[H|T],Opts}|Rest], NoExec, Load, Result) -> 203 case recompilep(coerce_2_list(H), NoExec, Load, Opts) of 204 error -> 205 process([{T,Opts}|Rest], NoExec, Load, [{H,error}|Result]); 206 Info -> 207 process([{T,Opts}|Rest], NoExec, Load, [{H,Info}|Result]) 208 end; 209process([], NoExec, _Load, Result) -> 210 if not NoExec -> 211 case lists:keysearch(error, 2, Result) of 212 {value,_} -> 213 {error,Result}; 214 false -> 215 {up_to_date,Result} 216 end; 217 true -> 218 Result 219 end. 220 221recompilep(File, NoExec, Load, Opts) -> 222 ObjName = lists:append(filename:basename(File), 223 code:objfile_extension()), 224 ObjFile = case lists:keysearch(outdir,1,Opts) of 225 {value,{outdir,OutDir}} -> 226 filename:join(coerce_2_list(OutDir),ObjName); 227 false -> 228 ObjName 229 end, 230 case exists(ObjFile) of 231 true -> 232 recompilep1(File, NoExec, Load, Opts, ObjFile); 233 false -> 234 recompile(File, NoExec, Load, Opts) 235 end. 236 237recompilep1(File, NoExec, Load, Opts, ObjFile) -> 238 {ok, Erl} = file:read_file_info(lists:append(File, ".erl")), 239 {ok, Obj} = file:read_file_info(ObjFile), 240 case {readable(Erl), writable(Obj)} of 241 {true, true} -> 242 recompilep1(Erl, Obj, File, NoExec, Load, Opts); 243 _ -> 244 error 245 end. 246 247recompilep1(#file_info{mtime=Te}, 248 #file_info{mtime=To}, File, NoExec, Load, Opts) when Te>To -> 249 recompile(File, NoExec, Load, Opts); 250recompilep1(_Erl, #file_info{mtime=To}, File, NoExec, Load, Opts) -> 251 recompile2(To, File, NoExec, Load, Opts). 252 253%% recompile2(ObjMTime, File, NoExec, Load, Opts) 254%% Check if file is of a later date than include files. 255recompile2(ObjMTime, File, NoExec, Load, Opts) -> 256 IncludePath = include_opt(Opts), 257 case check_includes(lists:append(File, ".erl"), IncludePath, ObjMTime) of 258 true -> 259 recompile(File, NoExec, Load, Opts); 260 false -> 261 up_to_date 262 end. 263 264include_opt([{i,Path}|Rest]) -> 265 [Path|include_opt(Rest)]; 266include_opt([_First|Rest]) -> 267 include_opt(Rest); 268include_opt([]) -> 269 []. 270 271%% recompile(File, NoExec, Load, Opts) 272%% Actually recompile and load the file, depending on the flags. 273%% Where load can be netload | load | noload 274 275recompile(File, NoExec, Load, Opts) -> 276 case do_recompile(File, NoExec, Load, Opts) of 277 {ok,_} -> ok; 278 Other -> Other 279 end. 280 281do_recompile(_File, true, _Load, _Opts) -> 282 out_of_date; 283do_recompile(File, false, Load, Opts) -> 284 io:format("Recompile: ~ts\n",[File]), 285 case compile:file(File, [report_errors, report_warnings |Opts]) of 286 Ok when is_tuple(Ok), element(1,Ok)==ok -> 287 maybe_load(element(2,Ok), Load, Opts); 288 _Error -> 289 error 290 end. 291 292maybe_load(_Mod, noload, _Opts) -> 293 ok; 294maybe_load(Mod, Load, Opts) -> 295 %% We have compiled File with options Opts. Find out where the 296 %% output file went to, and load it. 297 case compile:output_generated(Opts) of 298 true -> 299 Dir = proplists:get_value(outdir,Opts,"."), 300 do_load(Dir, Mod, Load); 301 false -> 302 io:format("** Warning: No object file created - nothing loaded **~n"), 303 ok 304 end. 305 306do_load(Dir, Mod, load) -> 307 code:purge(Mod), 308 case code:load_abs(filename:join(Dir, Mod),Mod) of 309 {module,Mod} -> 310 {ok,Mod}; 311 Other -> 312 Other 313 end; 314do_load(Dir, Mod, netload) -> 315 Obj = atom_to_list(Mod) ++ code:objfile_extension(), 316 Fname = filename:join(Dir, Obj), 317 case file:read_file(Fname) of 318 {ok,Bin} -> 319 rpc:eval_everywhere(code,load_binary,[Mod,Fname,Bin]), 320 {ok,Mod}; 321 Other -> 322 Other 323 end. 324 325exists(File) -> 326 case file:read_file_info(File) of 327 {ok, _} -> 328 true; 329 _ -> 330 false 331 end. 332 333readable(#file_info{access=read_write}) -> true; 334readable(#file_info{access=read}) -> true; 335readable(_) -> false. 336 337writable(#file_info{access=read_write}) -> true; 338writable(#file_info{access=write}) -> true; 339writable(_) -> false. 340 341coerce_2_list(X) when is_atom(X) -> 342 atom_to_list(X); 343coerce_2_list(X) -> 344 X. 345 346%%% If you an include file is found with a modification 347%%% time larger than the modification time of the object 348%%% file, return true. Otherwise return false. 349check_includes(File, IncludePath, ObjMTime) -> 350 Path = [filename:dirname(File)|IncludePath], 351 case epp:open(File, Path, []) of 352 {ok, Epp} -> 353 check_includes2(Epp, File, ObjMTime); 354 _Error -> 355 false 356 end. 357 358check_includes2(Epp, File, ObjMTime) -> 359 A1 = erl_anno:new(1), 360 case epp:parse_erl_form(Epp) of 361 {ok, {attribute, A1, file, {File, A1}}} -> 362 check_includes2(Epp, File, ObjMTime); 363 {ok, {attribute, A1, file, {IncFile, A1}}} -> 364 case file:read_file_info(IncFile) of 365 {ok, #file_info{mtime=MTime}} when MTime>ObjMTime -> 366 epp:close(Epp), 367 true; 368 _ -> 369 check_includes2(Epp, File, ObjMTime) 370 end; 371 {ok, _} -> 372 check_includes2(Epp, File, ObjMTime); 373 {eof, _} -> 374 epp:close(Epp), 375 false; 376 {error, _Error} -> 377 check_includes2(Epp, File, ObjMTime); 378 {warning, _Warning} -> 379 check_includes2(Epp, File, ObjMTime) 380 end. 381