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