Bladeren bron

merge rebar2's Refactor logic and optimizations in rebar_erlc_compiler:doterl_compile/4 #467

pull/323/head
Tristan Sloughter 10 jaren geleden
bovenliggende
commit
0648c6bc87
2 gewijzigde bestanden met toevoegingen van 162 en 286 verwijderingen
  1. +1
    -12
      src/rebar_base_compiler.erl
  2. +161
    -274
      src/rebar_erlc_compiler.erl

+ 1
- 12
src/rebar_base_compiler.erl Bestand weergeven

@ -102,21 +102,10 @@ remove_common_path1([Part | RestFilename], [Part | RestPath]) ->
remove_common_path1(FilenameParts, _) ->
filename:join(FilenameParts).
compile(Source, Config, CompileFn) ->
case CompileFn(Source, Config) of
ok ->
ok;
skipped ->
skipped;
Error ->
Error
end.
compile_each([], _Config, _CompileFn) ->
ok;
compile_each([Source | Rest], Config, CompileFn) ->
case compile(Source, Config, CompileFn) of
case CompileFn(Source, Config) of
ok ->
?DEBUG("~sCompiled ~s", [rebar_utils:indent(1), filename:basename(Source)]);
{ok, Warnings} ->

+ 161
- 274
src/rebar_erlc_compiler.erl Bestand weergeven

@ -33,15 +33,15 @@
-include("rebar.hrl").
-include_lib("stdlib/include/erl_compile.hrl").
-define(ERLCINFO_VSN, 1).
-define(ERLCINFO_VSN, 2).
-define(ERLCINFO_FILE, "erlcinfo").
-type erlc_info_v() :: {digraph:vertex(), term()} | 'false'.
-type erlc_info_e() :: {digraph:vertex(), digraph:vertex()}.
-type erlc_info() :: {list(erlc_info_v()), list(erlc_info_e())}.
-type erlc_info() :: {list(erlc_info_v()), list(erlc_info_e()), list(string())}.
-record(erlcinfo,
{
vsn = ?ERLCINFO_VSN :: pos_integer(),
info = {[], []} :: erlc_info()
info = {[], [], []} :: erlc_info()
}).
-define(RE_PREFIX, "^[^._]").
@ -104,7 +104,7 @@ compile(Config, Dir, OutDir) ->
doterl_compile(Config, Dir, OutDir).
-spec clean(rebar_state:t(), file:filename()) -> 'ok'.
clean(Config, AppDir) ->
clean(_Config, AppDir) ->
MibFiles = rebar_utils:find_files(filename:join(AppDir, "mibs"), ?RE_PREFIX".*\\.mib\$"),
MIBs = [filename:rootname(filename:basename(MIB)) || MIB <- MibFiles],
rebar_file_utils:delete_each(
@ -118,7 +118,7 @@ clean(Config, AppDir) ->
|| F <- YrlFiles ]),
%% Delete the build graph, if any
rebar_file_utils:rm_rf(erlcinfo_file(Config)),
rebar_file_utils:rm_rf(erlcinfo_file()),
%% Erlang compilation is recursive, so it's possible that we have a nested
%% directory structure in ebin with .beam files within. As such, we want
@ -139,7 +139,6 @@ doterl_compile(State, Dir, ODir) ->
doterl_compile(State, Dir, ODir, [], ErlOpts).
doterl_compile(Config, Dir, OutDir, MoreSources, ErlOpts) ->
ErlFirstFilesConf = rebar_state:get(Config, erl_first_files, []),
?DEBUG("erl_opts ~p", [ErlOpts]),
%% Support the src_dirs option allowing multiple directories to
%% contain erlang source. This might be used, for example, should
@ -147,164 +146,84 @@ doterl_compile(Config, Dir, OutDir, MoreSources, ErlOpts) ->
SrcDirs = [filename:join(Dir, X) || X <- proplists:get_value(src_dirs, ErlOpts, ["src"])],
AllErlFiles = gather_src(SrcDirs, []) ++ MoreSources,
%% Issue: rebar/rebar3#140 (fix matching based on same path + order of
%% erl_first_files)
ErlFirstFiles = get_erl_first_files(ErlFirstFilesConf, AllErlFiles),
RestErls = [ File || File <- AllErlFiles,
not lists:member(File, ErlFirstFiles) ],
%% Make sure that ebin/ exists and is on the path
ok = filelib:ensure_dir(filename:join(OutDir, "dummy.beam")),
CurrPath = code:get_path(),
true = code:add_path(filename:absname(OutDir)),
OutDir1 = proplists:get_value(outdir, ErlOpts, OutDir),
G = init_erlcinfo(Config, AllErlFiles),
%% Split RestErls so that files which are depended on are treated
%% like erl_first_files.
{OtherFirstErls, OtherErls} =
lists:partition(
fun(F) ->
Children = get_children(G, F),
log_files(?FMT("Files dependent on ~s", [F]), Children),
case erls(Children) of
[] ->
%% There are no files dependent on this file.
false;
_ ->
%% There are some files dependent on the file.
%% Thus the file has higher priority
%% and should be compiled in the first place.
true
end
end, RestErls),
%% Dependencies of OtherFirstErls that must be compiled first.
OtherFirstErlsDeps = lists:flatmap(
fun(Erl) -> erls(get_parents(G, Erl)) end,
OtherFirstErls),
%% NOTE: In case the way we retrieve OtherFirstErlsDeps or merge
%% it with OtherFirstErls does not result in the correct compile
%% priorities, or the method in use proves to be too slow for
%% certain projects, consider using a more elaborate method (maybe
%% digraph_utils) or alternatively getting and compiling the .erl
%% parents of an individual Source in internal_erl_compile. By not
%% handling this in internal_erl_compile, we also avoid extra
%% needs_compile/2 calls.
FirstErls = ErlFirstFiles ++ uo_merge(OtherFirstErlsDeps, OtherFirstErls),
G = init_erlcinfo(proplists:get_all_values(i, ErlOpts), AllErlFiles),
NeededErlFiles = needed_files(G, ErlOpts, Dir, OutDir1, AllErlFiles),
ErlFirstFiles = erl_first_files(Config, NeededErlFiles),
{DepErls, OtherErls} = lists:partition(
fun(Source) -> digraph:in_degree(G, Source) > 0 end,
[File || File <- NeededErlFiles, not lists:member(File, ErlFirstFiles)]),
DepErlsOrdered = digraph_utils:topsort(digraph_utils:subgraph(G, DepErls)),
FirstErls = ErlFirstFiles ++ lists:reverse(DepErlsOrdered),
?DEBUG("Files to compile first: ~p", [FirstErls]),
rebar_base_compiler:run(
Config, FirstErls, OtherErls,
fun(S, C) ->
internal_erl_compile(C, Dir, S, OutDir1, ErlOpts, G)
internal_erl_compile(C, Dir, S, OutDir1, ErlOpts)
end),
true = code:set_path(CurrPath),
ok.
%%
%% Return all .erl files from a list of files
%%
erls(Files) ->
[Erl || Erl <- Files, filename:extension(Erl) =:= ".erl"].
%%
%% Return a list of erl_first_files in order as specified in rebar.config
%% using the path from AllFiles.
%%
get_erl_first_files(FirstFiles, AllFiles) ->
BaseFirstFiles = [filename:basename(F) || F <- FirstFiles],
IndexedAllFiles = [{filename:basename(F), F} || F <- AllFiles ],
[ proplists:get_value(FileName, IndexedAllFiles) ||
FileName <- BaseFirstFiles,
proplists:is_defined(FileName, IndexedAllFiles) ].
%%
%% Return a list without duplicates while preserving order
%%
ulist(L) ->
ulist(L, []).
ulist([H|T], Acc) ->
case lists:member(H, T) of
true ->
ulist(T, Acc);
false ->
ulist(T, [H|Acc])
end;
ulist([], Acc) ->
lists:reverse(Acc).
%%
%% Merge two lists without duplicates while preserving order
%%
uo_merge(L1, L2) ->
lists:foldl(fun(E, Acc) -> u_add_element(E, Acc) end, ulist(L1), L2).
u_add_element(Elem, [Elem|_]=Set) -> Set;
u_add_element(Elem, [E1|Set]) -> [E1|u_add_element(Elem, Set)];
u_add_element(Elem, []) -> [Elem].
-spec include_path(file:filename(),
rebar_state:t()) -> [file:filename(), ...].
include_path(Source, Config) ->
ErlOpts = rebar_state:get(Config, erl_opts, []),
Dir = filename:join(rebar_utils:droplast(filename:split(filename:dirname(Source)))),
lists:usort([filename:join(Dir, "include"), filename:dirname(Source)]
++ proplists:get_all_values(i, ErlOpts)).
-spec needs_compile(file:filename(), file:filename(),
list(), [string()]) -> boolean().
needs_compile(Source, Target, Opts, Parents) ->
TargetLastMod = filelib:last_modified(Target),
F = fun(I) -> source_changed(TargetLastMod, I) end,
lists:any(F, [Source] ++ Parents) orelse opts_changed(Opts, Target).
source_changed(TargetLastMod, I) -> TargetLastMod < filelib:last_modified(I).
opts_changed(NewOpts, Target) ->
case compile_info(Target) of
{ok, Opts} -> lists:sort(Opts) =/= lists:sort(NewOpts);
_ -> true
end.
compile_info(Target) ->
case beam_lib:chunks(Target, [compile_info]) of
{ok, {_mod, Chunks}} ->
CompileInfo = proplists:get_value(compile_info, Chunks, []),
{ok, proplists:get_value(options, CompileInfo, [])};
{error, beam_lib, Reason} ->
?WARN("Couldn't read debug info from ~p for reason: ~p", [Target, Reason]),
{error, Reason}
erl_first_files(Config, NeededErlFiles) ->
%% NOTE: rebar_config:get_local perhaps?
ErlFirstFilesConf = rebar_state:get(Config, erl_first_files, []),
%% NOTE: order of files in ErlFirstFiles is important!
[File || File <- ErlFirstFilesConf, lists:member(File, NeededErlFiles)].
%% Get subset of SourceFiles which need to be recompiled, respecting
%% dependencies induced by given graph G.
needed_files(G, ErlOpts, Dir, OutDir, SourceFiles) ->
lists:filter(fun(Source) ->
Target = target_base(OutDir, Source) ++ ".beam",
Opts = [{outdir, filename:dirname(Target)}
,{i, filename:join(Dir, "include")}] ++ ErlOpts,
digraph:vertex(G, Source) > {Source, filelib:last_modified(Target)}
orelse opts_changed(Opts, Target)
end, SourceFiles).
opts_changed(Opts, Target) ->
Basename = filename:basename(Target, ".beam"),
Dirname = filename:dirname(Target),
ObjectFile = filename:join([Dirname, Basename]),
case code:load_abs(ObjectFile) of
{module, Mod} ->
Compile = Mod:module_info(compile),
lists:sort(Opts) =/= lists:sort(proplists:get_value(options,
Compile,
undefined));
{error, _} -> true
end.
check_erlcinfo(_Config, #erlcinfo{vsn=?ERLCINFO_VSN}) ->
ok;
check_erlcinfo(Config, #erlcinfo{vsn=Vsn}) ->
?ABORT("~s file version is incompatible. expected: ~b got: ~b",
[erlcinfo_file(Config), ?ERLCINFO_VSN, Vsn]);
check_erlcinfo(Config, _) ->
?ABORT("~s file is invalid. Please delete before next run.",
[erlcinfo_file(Config)]).
erlcinfo_file(_Config) ->
erlcinfo_file() ->
filename:join(rebar_dir:local_cache_dir(), ?ERLCINFO_FILE).
init_erlcinfo(Config, Erls) ->
G = restore_erlcinfo(Config),
%% Get a unique list of dirs based on the source files' locations.
%% This is used for finding files in sub dirs of the configured
%% src_dirs. For example, src/sub_dir/foo.erl.
Dirs = sets:to_list(lists:foldl(
fun(Erl, Acc) ->
Dir = filename:dirname(Erl),
sets:add_element(Dir, Acc)
end, sets:new(), Erls)),
Updates = [update_erlcinfo(G, Erl, include_path(Erl, Config) ++ Dirs)
|| Erl <- Erls],
Modified = lists:member(modified, Updates),
ok = store_erlcinfo(G, Config, Modified),
%% Get dependency graph of given Erls files and their dependencies (header files,
%% parse transforms, behaviours etc.) located in their directories or given
%% InclDirs. Note that last modification times stored in vertices already respect
%% dependencies induced by given graph G.
init_erlcinfo(InclDirs, Erls) ->
G = digraph:new([acyclic]),
try restore_erlcinfo(G, InclDirs)
catch
_:_ ->
?WARN("Failed to restore ~s file. Discarding it.~n", [erlcinfo_file()]),
file:delete(erlcinfo_file())
end,
Dirs = source_and_include_dirs(InclDirs, Erls),
Modified = lists:foldl(update_erlcinfo_fun(G, Dirs), false, Erls),
if Modified -> store_erlcinfo(G, InclDirs); not Modified -> ok end,
G.
update_erlcinfo(G, Source, Dirs) ->
source_and_include_dirs(InclDirs, Erls) ->
SourceDirs = lists:map(fun filename:dirname/1, Erls),
lists:usort(["include" | InclDirs ++ SourceDirs]).
update_erlcinfo(G, Dirs, Source) ->
case digraph:vertex(G, Source) of
{_, LastUpdated} ->
case filelib:last_modified(Source) of
@ -315,77 +234,75 @@ update_erlcinfo(G, Source, Dirs) ->
digraph:del_vertex(G, Source),
modified;
LastModified when LastUpdated < LastModified ->
modify_erlcinfo(G, Source, Dirs),
modified;
modify_erlcinfo(G, Source, LastModified, Dirs);
_ ->
unmodified
Modified = lists:foldl(
update_erlcinfo_fun(G, Dirs),
false, digraph:out_neighbours(G, Source)),
MaxModified = update_max_modified_deps(G, Source),
case Modified orelse MaxModified > LastUpdated of
true -> modified;
false -> unmodified
end
end;
false ->
modify_erlcinfo(G, Source, Dirs),
modified
modify_erlcinfo(G, Source, filelib:last_modified(Source), Dirs)
end.
update_erlcinfo_fun(G, Dirs) ->
fun(Erl, Modified) ->
case update_erlcinfo(G, Dirs, Erl) of
modified -> true;
unmodified -> Modified
end
end.
modify_erlcinfo(G, Source, Dirs) ->
update_max_modified_deps(G, Source) ->
MaxModified = lists:max(lists:map(
fun(File) -> {_, MaxModified} = digraph:vertex(G, File), MaxModified end,
[Source|digraph:out_neighbours(G, Source)])),
digraph:add_vertex(G, Source, MaxModified),
MaxModified.
modify_erlcinfo(G, Source, LastModified, Dirs) ->
{ok, Fd} = file:open(Source, [read]),
Incls = parse_attrs(Fd, []),
AbsIncls = expand_file_names(Incls, Dirs),
ok = file:close(Fd),
LastUpdated = {date(), time()},
digraph:add_vertex(G, Source, LastUpdated),
digraph:add_vertex(G, Source, LastModified),
digraph:del_edges(G, digraph:out_edges(G, Source)),
lists:foreach(
fun(Incl) ->
update_erlcinfo(G, Incl, Dirs),
update_erlcinfo(G, Dirs, Incl),
digraph:add_edge(G, Source, Incl)
end, AbsIncls).
end, AbsIncls),
modified.
restore_erlcinfo(Config) ->
File = erlcinfo_file(Config),
G = digraph:new(),
case file:read_file(File) of
restore_erlcinfo(G, InclDirs) ->
case file:read_file(erlcinfo_file()) of
{ok, Data} ->
try binary_to_term(Data) of
Erlcinfo ->
ok = check_erlcinfo(Config, Erlcinfo),
#erlcinfo{info=ErlcInfo} = Erlcinfo,
{Vs, Es} = ErlcInfo,
lists:foreach(
fun({V, LastUpdated}) ->
digraph:add_vertex(G, V, LastUpdated)
end, Vs),
lists:foreach(
fun({V1, V2}) ->
digraph:add_edge(G, V1, V2)
end, Es)
catch
error:badarg ->
?ERROR(
"Failed (binary_to_term) to restore rebar info file."
" Discard file.", []),
ok
end;
_Err ->
% Since externally passed InclDirs can influence erlcinfo graph (see
% modify_erlcinfo), we have to check here that they didn't change.
#erlcinfo{vsn=?ERLCINFO_VSN, info={Vs, Es, InclDirs}} =
binary_to_term(Data),
lists:foreach(
fun({V, LastUpdated}) ->
digraph:add_vertex(G, V, LastUpdated)
end, Vs),
lists:foreach(
fun({_, V1, V2, _}) ->
digraph:add_edge(G, V1, V2)
end, Es);
{error, _} ->
ok
end,
G.
end.
store_erlcinfo(_G, _Config, _Modified = false) ->
ok;
store_erlcinfo(G, Config, _Modified) ->
Vs = lists:map(
fun(V) ->
digraph:vertex(G, V)
end, digraph:vertices(G)),
Es = lists:flatmap(
fun({V, _}) ->
lists:map(
fun(E) ->
{_, V1, V2, _} = digraph:edge(G, E),
{V1, V2}
end, digraph:out_edges(G, V))
end, Vs),
File = erlcinfo_file(Config),
store_erlcinfo(G, InclDirs) ->
Vs = lists:map(fun(V) -> digraph:vertex(G, V) end, digraph:vertices(G)),
Es = lists:map(fun(E) -> digraph:edge(G, E) end, digraph:edges(G)),
File = erlcinfo_file(),
ok = filelib:ensure_dir(File),
Data = term_to_binary(#erlcinfo{info={Vs, Es}}, [{compressed, 9}]),
Data = term_to_binary(#erlcinfo{info={Vs, Es, InclDirs}}, [{compressed, 2}]),
file:write_file(File, Data).
%% NOTE: If, for example, one of the entries in Files, refers to
@ -420,50 +337,27 @@ expand_file_names(Files, Dirs) ->
end
end, Files).
-spec get_parents(rebar_digraph(), file:filename()) -> [file:filename()].
get_parents(G, Source) ->
%% Return all files which the Source depends upon.
digraph_utils:reachable_neighbours([Source], G).
-spec get_children(rebar_digraph(), file:filename()) -> [file:filename()].
get_children(G, Source) ->
%% Return all files dependent on the Source.
digraph_utils:reaching_neighbours([Source], G).
-spec internal_erl_compile(rebar_state:t(), file:filename(), file:filename(),
file:filename(), list(),
rebar_digraph()) -> 'ok' | 'skipped'.
internal_erl_compile(Config, Dir, Source, OutDir, ErlOpts, G) ->
%% Determine the target name and includes list by inspecting the source file
Module = filename:basename(Source, ".erl"),
Parents = get_parents(G, Source),
log_files(?FMT("Dependencies of ~s", [Source]), Parents),
%% Construct the target filename
Target = filename:join([OutDir | string:tokens(Module, ".")]) ++ ".beam",
-spec internal_erl_compile(rebar_config:config(), file:filename(), file:filename(),
file:filename(), list()) -> ok | {ok, any()} | {error, any(), any()}.
internal_erl_compile(Config, Dir, Module, OutDir, ErlOpts) ->
Target = target_base(OutDir, Module) ++ ".beam",
ok = filelib:ensure_dir(Target),
%% Construct the compile opts
Opts = [{outdir, filename:dirname(Target)}] ++
ErlOpts ++ [{i, filename:join(Dir, "include")}],
%% If the file needs compilation, based on last mod date of includes or
%% the target
case needs_compile(Source, Target, Opts, Parents) of
true ->
case compile:file(Source, Opts ++ [return]) of
{ok, _Mod} ->
ok;
{ok, _Mod, Ws} ->
rebar_base_compiler:ok_tuple(Config, Source, Ws);
{error, Es, Ws} ->
rebar_base_compiler:error_tuple(Config, Source,
Es, Ws, Opts)
end;
false ->
skipped
Opts = [{outdir, filename:dirname(Target)}] ++ ErlOpts ++
[{i, filename:join(Dir, "include")}, return],
case compile:file(Module, Opts) of
{ok, _Mod} ->
ok;
{ok, _Mod, Ws} ->
rebar_base_compiler:ok_tuple(Config, Module, Ws);
{error, Es, Ws} ->
rebar_base_compiler:error_tuple(Config, Module, Es, Ws, Opts)
end.
target_base(OutDir, Source) ->
Module = filename:basename(Source, ".erl"),
filename:join([OutDir|string:tokens(Module, ".")]).
-spec compile_mib(file:filename(), file:filename(),
rebar_state:t()) -> 'ok'.
compile_mib(Source, Target, Config) ->
@ -507,7 +401,7 @@ compile_xrl_yrl(Config, Source, Target, Opts, Mod) ->
Dir = rebar_state:dir(Config),
Opts1 = [{includefile, filename:join(Dir, I)} || {includefile, I} <- Opts,
filename:pathtype(I) =:= relative],
case needs_compile(Source, Target, Opts1, []) of
case filelib:last_modified(Source) > filelib:last_modified(Target) of
true ->
case Mod:file(Source, Opts1 ++ [{return, true}]) of
{ok, _} ->
@ -556,21 +450,15 @@ parse_attrs(Fd, Includes) ->
end.
process_attr(Form, Includes) ->
try
AttrName = erl_syntax:atom_value(erl_syntax:attribute_name(Form)),
process_attr(AttrName, Form, Includes)
catch _:_ ->
%% TODO: We should probably try to be more specific here
%% and not suppress all errors.
Includes
end.
AttrName = erl_syntax:atom_value(erl_syntax:attribute_name(Form)),
process_attr(AttrName, Form, Includes).
process_attr(import, Form, Includes) ->
case erl_syntax_lib:analyze_import_attribute(Form) of
{Mod, _Funs} ->
[atom_to_list(Mod) ++ ".erl"|Includes];
[module_to_erl(Mod)|Includes];
Mod ->
[atom_to_list(Mod) ++ ".erl"|Includes]
[module_to_erl(Mod)|Includes]
end;
process_attr(file, Form, Includes) ->
{File, _} = erl_syntax_lib:analyze_file_attribute(Form),
@ -582,29 +470,36 @@ process_attr(include, Form, Includes) ->
process_attr(include_lib, Form, Includes) ->
[FileNode] = erl_syntax:attribute_arguments(Form),
RawFile = erl_syntax:string_value(FileNode),
File = maybe_expand_include_lib_path(RawFile),
[File|Includes];
maybe_expand_include_lib_path(RawFile) ++ Includes;
process_attr(behaviour, Form, Includes) ->
[FileNode] = erl_syntax:attribute_arguments(Form),
File = erl_syntax:atom_name(FileNode) ++ ".erl",
File = module_to_erl(erl_syntax:atom_value(FileNode)),
[File|Includes];
process_attr(compile, Form, Includes) ->
[Arg] = erl_syntax:attribute_arguments(Form),
case erl_syntax:concrete(Arg) of
{parse_transform, Mod} ->
[atom_to_list(Mod) ++ ".erl"|Includes];
[module_to_erl(Mod)|Includes];
{core_transform, Mod} ->
[atom_to_list(Mod) ++ ".erl"|Includes];
[module_to_erl(Mod)|Includes];
L when is_list(L) ->
lists:foldl(
fun({parse_transform, M}, Acc) ->
[atom_to_list(M) ++ ".erl"|Acc];
({core_transform, M}, Acc) ->
[atom_to_list(M) ++ ".erl"|Acc];
fun({parse_transform, Mod}, Acc) ->
[module_to_erl(Mod)|Acc];
({core_transform, Mod}, Acc) ->
[module_to_erl(Mod)|Acc];
(_, Acc) ->
Acc
end, Includes, L)
end.
end, Includes, L);
_ ->
Includes
end;
process_attr(_, _Form, Includes) ->
Includes.
module_to_erl(Mod) ->
atom_to_list(Mod) ++ ".erl".
%% Given the filename from an include_lib attribute, if the path
%% exists, return unmodified, or else get the absolute ERL_LIBS
@ -612,7 +507,7 @@ process_attr(compile, Form, Includes) ->
maybe_expand_include_lib_path(File) ->
case filelib:is_regular(File) of
true ->
File;
[File];
false ->
expand_include_lib_path(File)
end.
@ -627,8 +522,10 @@ expand_include_lib_path(File) ->
Split = filename:split(filename:dirname(File)),
Lib = hd(Split),
SubDir = filename:join(tl(Split)),
Dir = code:lib_dir(list_to_atom(Lib), list_to_atom(SubDir)),
filename:join(Dir, File1).
case code:lib_dir(list_to_atom(Lib), list_to_atom(SubDir)) of
{error, bad_name} -> [];
Dir -> [filename:join(Dir, File1)]
end.
%%
%% Ensure all files in a list are present and abort if one is missing
@ -642,13 +539,3 @@ check_file(File) ->
false -> ?ABORT("File ~p is missing, aborting\n", [File]);
true -> File
end.
%% Print prefix followed by list of files. If the list is empty, print
%% on the same line, otherwise use a separate line.
log_files(Prefix, Files) ->
case Files of
[] ->
?DEBUG("~s: ~p", [Prefix, Files]);
_ ->
?DEBUG("~s:~n~p", [Prefix, Files])
end.

Laden…
Annuleren
Opslaan