summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--THANKS1
-rw-r--r--src/rebar_base_compiler.erl13
-rw-r--r--src/rebar_erlc_compiler.erl466
-rw-r--r--test/rebar_compile_SUITE.erl41
4 files changed, 217 insertions, 304 deletions
diff --git a/THANKS b/THANKS
index 124943d..983d883 100644
--- a/THANKS
+++ b/THANKS
@@ -132,3 +132,4 @@ Tristan Sloughter
Kelly McLaughlin
Martin Karlsson
Pierre Fenoll
+David Kubecka \ No newline at end of file
diff --git a/src/rebar_base_compiler.erl b/src/rebar_base_compiler.erl
index 265ea80..df950d7 100644
--- a/src/rebar_base_compiler.erl
+++ b/src/rebar_base_compiler.erl
@@ -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} ->
diff --git a/src/rebar_erlc_compiler.erl b/src/rebar_erlc_compiler.erl
index 0ea09dc..5bd04d2 100644
--- a/src/rebar_erlc_compiler.erl
+++ b/src/rebar_erlc_compiler.erl
@@ -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,81 @@ 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) ->
+ 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) ->
+ TargetBase = target_base(OutDir, Source),
+ Target = TargetBase ++ ".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, TargetBase)
+ end, SourceFiles).
+
+opts_changed(Opts, ObjectFile) ->
+ case code:load_abs(ObjectFile) of
+ {module, Mod} ->
+ Compile = Mod:module_info(compile),
+ lists:sort(Opts) =/= lists:sort(proplists:get_value(options,
+ Compile,
+ []));
+ {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 +231,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, filename:dirname(Source), 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), filename:dirname(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, Dir, Dirs) ->
{ok, Fd} = file:open(Source, [read]),
- Incls = parse_attrs(Fd, []),
+ Incls = parse_attrs(Fd, [], Dir),
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 +334,26 @@ 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",
- 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
+-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),
+ 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) ->
+ filename:join(OutDir, filename:basename(Source, ".erl")).
+
-spec compile_mib(file:filename(), file:filename(),
rebar_state:t()) -> 'ok'.
compile_mib(Source, Target, Config) ->
@@ -507,7 +397,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 needs_compile(Source, Target) of
true ->
case Mod:file(Source, Opts1 ++ [{return, true}]) of
{ok, _} ->
@@ -522,6 +412,9 @@ compile_xrl_yrl(Config, Source, Target, Opts, Mod) ->
skipped
end.
+needs_compile(Source, Target) ->
+ filelib:last_modified(Source) > filelib:last_modified(Target).
+
gather_src([], Srcs) ->
Srcs;
gather_src([Dir|Rest], Srcs) ->
@@ -539,80 +432,81 @@ delete_dir(Dir, Subdirs) ->
lists:foreach(fun(D) -> delete_dir(D, dirs(D)) end, Subdirs),
file:del_dir(Dir).
-parse_attrs(Fd, Includes) ->
+parse_attrs(Fd, Includes, Dir) ->
case io:parse_erl_form(Fd, "") of
{ok, Form, _Line} ->
case erl_syntax:type(Form) of
attribute ->
- NewIncludes = process_attr(Form, Includes),
- parse_attrs(Fd, NewIncludes);
+ NewIncludes = process_attr(Form, Includes, Dir),
+ parse_attrs(Fd, NewIncludes, Dir);
_ ->
- parse_attrs(Fd, Includes)
+ parse_attrs(Fd, Includes, Dir)
end;
{eof, _} ->
Includes;
_Err ->
- parse_attrs(Fd, Includes)
+ parse_attrs(Fd, Includes, Dir)
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.
+process_attr(Form, Includes, Dir) ->
+ AttrName = erl_syntax:atom_value(erl_syntax:attribute_name(Form)),
+ process_attr(AttrName, Form, Includes, Dir).
-process_attr(import, Form, Includes) ->
+process_attr(import, Form, Includes, _Dir) ->
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) ->
+process_attr(file, Form, Includes, _Dir) ->
{File, _} = erl_syntax_lib:analyze_file_attribute(Form),
[File|Includes];
-process_attr(include, Form, Includes) ->
+process_attr(include, Form, Includes, _Dir) ->
[FileNode] = erl_syntax:attribute_arguments(Form),
File = erl_syntax:string_value(FileNode),
[File|Includes];
-process_attr(include_lib, Form, Includes) ->
+process_attr(include_lib, Form, Includes, Dir) ->
[FileNode] = erl_syntax:attribute_arguments(Form),
RawFile = erl_syntax:string_value(FileNode),
- File = maybe_expand_include_lib_path(RawFile),
- [File|Includes];
-process_attr(behaviour, Form, Includes) ->
+ maybe_expand_include_lib_path(RawFile, Dir) ++ Includes;
+process_attr(behaviour, Form, Includes, _Dir) ->
[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) ->
+process_attr(compile, Form, Includes, _Dir) ->
[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, _Dir) ->
+ 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
%% path.
-maybe_expand_include_lib_path(File) ->
- case filelib:is_regular(File) of
+maybe_expand_include_lib_path(File, Dir) ->
+ case filelib:is_regular(filename:join(Dir, File)) of
true ->
- File;
+ [filename:join(Dir, File)];
false ->
expand_include_lib_path(File)
end.
@@ -627,8 +521,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 +538,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.
diff --git a/test/rebar_compile_SUITE.erl b/test/rebar_compile_SUITE.erl
index ede1a52..c0308d6 100644
--- a/test/rebar_compile_SUITE.erl
+++ b/test/rebar_compile_SUITE.erl
@@ -11,6 +11,7 @@
build_checkout_apps/1,
build_checkout_deps/1,
build_all_srcdirs/1,
+ recompile_when_hrl_changes/1,
recompile_when_opts_change/1,
dont_recompile_when_opts_dont_change/1,
dont_recompile_yrl_or_xrl/1,
@@ -40,7 +41,7 @@ end_per_testcase(_, _Config) ->
all() ->
[build_basic_app, build_release_apps,
build_checkout_apps, build_checkout_deps,
- build_all_srcdirs,
+ build_all_srcdirs, recompile_when_hrl_changes,
recompile_when_opts_change, dont_recompile_when_opts_dont_change,
dont_recompile_yrl_or_xrl, deps_in_path, checkout_priority, compile_plugins].
@@ -131,6 +132,43 @@ build_all_srcdirs(Config) ->
%% check the extra src_dir was linked into the _build dir
true = filelib:is_dir(filename:join([AppDir, "_build", "default", "lib", Name, "extra"])).
+recompile_when_hrl_changes(Config) ->
+ AppDir = ?config(apps, Config),
+
+ Name = rebar_test_utils:create_random_name("app1_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_app(AppDir, Name, Vsn, [kernel, stdlib]),
+
+
+ ExtraSrc = <<"-module(test_header_include).\n"
+ "-export([main/0]).\n"
+ "-include(\"test_header_include.hrl\").\n"
+ "main() -> ?SOME_DEFINE.\n">>,
+
+ ExtraHeader = <<"-define(SOME_DEFINE, true).\n">>,
+ HeaderFile = filename:join([AppDir, "src", "test_header_include.hrl"]),
+ ok = file:write_file(filename:join([AppDir, "src", "test_header_include.erl"]), ExtraSrc),
+ ok = file:write_file(HeaderFile, ExtraHeader),
+
+ rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}),
+
+ EbinDir = filename:join([AppDir, "_build", "default", "lib", Name, "ebin"]),
+ {ok, Files} = file:list_dir(EbinDir),
+ ModTime = [filelib:last_modified(filename:join([EbinDir, F]))
+ || F <- Files, filename:extension(F) == ".beam"],
+
+ timer:sleep(1000),
+
+ os:cmd("touch " ++ HeaderFile),
+
+ rebar_test_utils:run_and_check(Config, [], ["compile"], {ok, [{app, Name}]}),
+
+ {ok, NewFiles} = file:list_dir(EbinDir),
+ NewModTime = [filelib:last_modified(filename:join([EbinDir, F]))
+ || F <- NewFiles, filename:extension(F) == ".beam"],
+
+ ?assert(ModTime =/= NewModTime).
+
recompile_when_opts_change(Config) ->
AppDir = ?config(apps, Config),
@@ -157,7 +195,6 @@ recompile_when_opts_change(Config) ->
?assert(ModTime =/= NewModTime).
-
dont_recompile_when_opts_dont_change(Config) ->
AppDir = ?config(apps, Config),