Current section
Files
Jump to
Current section
Files
src/pc_compilation.erl
%% -------------------------------------------------------------------
%%
%% This file contains substantial portions of the original rebar_port_compiler.
%% Special thanks to all the folks that contributed to that effort.
%%
%% -------------------------------------------------------------------
%%
%% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
%%
%% Permission is hereby granted, free of charge, to any person obtaining a copy
%% of this software and associated documentation files (the "Software"), to deal
%% in the Software without restriction, including without limitation the rights
%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%% copies of the Software, and to permit persons to whom the Software is
%% furnished to do so, subject to the following conditions:
%%
%% The above copyright notice and this permission notice shall be included in
%% all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%% THE SOFTWARE.
%%
%% -------------------------------------------------------------------
-module(pc_compilation).
-export([compile_and_link/2, clean/2, compiler/1]).
-export_type([]).
%%%===================================================================
%%% API
%%%===================================================================
-spec compile_and_link(State :: rebar_state:t(),
Specs :: [pc_port_specs:spec()]) -> ok.
compile_and_link(State, Specs) ->
%% Compile each of the sources
NewBins = compile_sources(State, Specs),
%% Make sure that the target directories exist
lists:foreach(fun(Spec) ->
Target = pc_port_specs:target(Spec),
ok = filelib:ensure_dir(filename:join(rebar_state:dir(State), Target))
end, Specs),
%% Only relink if necessary, given the Target
%% and list of new binaries
lists:foreach(
fun(Spec) ->
Target = pc_port_specs:target(Spec),
Bins = pc_port_specs:objects(Spec),
AllBins = [sets:from_list(Bins),
sets:from_list(NewBins)],
Intersection = sets:intersection(AllBins),
case needs_link(Target, sets:to_list(Intersection)) of
true ->
LinkLang = pc_port_specs:link_lang(Spec),
LinkTemplate = select_link_template(LinkLang, Target),
Env = pc_port_specs:environment(Spec),
Cmd = expand_command(LinkTemplate, Env,
string:join(Bins, " "),
Target),
rebar_api:info("Linking ~s", [Target]),
rebar_utils:sh(Cmd, [{env, Env}, {cd, rebar_state:dir(State)}]);
false ->
ok
end
end, Specs).
clean(_State, Specs) ->
lists:foreach(fun(Spec) ->
Target = pc_port_specs:target(Spec),
Objects = pc_port_specs:objects(Spec),
rebar_file_utils:delete_each([Target]),
rebar_file_utils:delete_each(Objects),
rebar_file_utils:delete_each(port_deps(Objects))
end, Specs).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
port_deps(SourceFiles) ->
[pc_util:replace_extension(O, ".d") || O <- SourceFiles].
%%
%% == compilation ==
%%
compile_sources(Config, Specs) ->
{NewBins, Db} =
lists:foldl(
fun(Spec, Acc) ->
Sources = pc_port_specs:sources(Spec),
Type = pc_port_specs:type(Spec),
Env = pc_port_specs:environment(Spec),
compile_each(Config, Sources, Type, Env, Acc)
end, {[], []}, Specs),
%% Rewrite clang compile commands database file only if something
%% was compiled.
case {NewBins, rebar_state:get(Config, pc_clang_db, false)} of
{[], _} ->
ok;
{_, true} ->
{ok, ClangDbFile} = file:open("compile_commands.json", [write]),
ok = io:fwrite(ClangDbFile, "[~n", []),
lists:foreach(fun(E) -> ok = io:fwrite(ClangDbFile, E, []) end, Db),
ok = io:fwrite(ClangDbFile, "]~n", []),
ok = file:close(ClangDbFile);
_ ->
ok
end,
NewBins.
compile_each(_State, [], _Type, _Env, {NewBins, CDB}) ->
{lists:reverse(NewBins), lists:reverse(CDB)};
compile_each(State, [Source | Rest], Type, Env, {NewBins, CDB}) ->
Ext = filename:extension(Source),
Bin = pc_util:replace_extension(Source, Ext, ".o"),
Template = select_compile_template(Type, compiler(Ext)),
Cmd = expand_command(Template, Env, Source, Bin),
CDBEnt = cdb_entry(State, Source, Cmd, Rest),
NewCDB = [CDBEnt | CDB],
case needs_compile(Source, Bin) of
true ->
ShOpts = [ {env, Env}
, return_on_error
, {use_stdout, false}
, {cd, rebar_state:dir(State)}
],
exec_compiler(State, Source, Cmd, ShOpts),
compile_each(State, Rest, Type, Env,
{[Bin | NewBins], NewCDB});
false ->
compile_each(State, Rest, Type, Env, {NewBins, NewCDB})
end.
%% Generate a clang compilation db entry for Src and Cmd
cdb_entry(State, Src, Cmd, SrcRest) ->
%% Omit all variables from cmd, and use that as cmd in
%% CDB, because otherwise clang-* will complain about it.
CDBCmd = string:join(
lists:filter(
fun("$"++_) -> false;
(_) -> true
end,
string:tokens(Cmd, " ")),
" "),
Cwd = rebar_state:dir(State),
%% If there are more source files, make sure we end the CDB entry
%% with a comma.
Sep = case SrcRest of
[] -> "~n";
_ -> ",~n"
end,
%% CDB entry
lists:flatten(
io_lib:format(
"{ \"file\" : ~p~n"
", \"directory\" : ~p~n"
", \"command\" : ~p~n"
"}~s",
[Src, Cwd, CDBCmd, Sep])).
%%
%% Choose a compiler variable, based on a provided extension
%%
compiler(".cc") -> "$CXX";
compiler(".cp") -> "$CXX";
compiler(".cxx") -> "$CXX";
compiler(".cpp") -> "$CXX";
compiler(".CPP") -> "$CXX";
compiler(".c++") -> "$CXX";
compiler(".C") -> "$CXX";
compiler(_) -> "$CC".
expand_command(TmplName, Env, InFiles, OutFile) ->
Cmd0 = proplists:get_value(TmplName, Env),
Cmd1 = rebar_api:expand_env_variable(Cmd0, "PORT_IN_FILES", InFiles),
rebar_api:expand_env_variable(Cmd1, "PORT_OUT_FILE", OutFile).
exec_compiler(_Config, Source, Cmd, ShOpts) ->
case rebar_utils:sh(Cmd, ShOpts) of
{error, {_RC, RawError}} ->
AbsSource = filename:absname(Source),
rebar_api:info("Compiling ~s", [AbsSource]),
Error = re:replace(RawError, Source, AbsSource,
[{return, list}, global]),
rebar_api:error("~s", [Error]),
rebar_api:abort();
{ok, Output} ->
rebar_api:info("Compiling ~s", [Source]),
rebar_api:debug("~s", [Output])
end.
select_compile_template(drv, Compiler) ->
select_compile_drv_template(Compiler);
select_compile_template(exe, Compiler) ->
select_compile_exe_template(Compiler).
select_compile_drv_template("$CC") -> "DRV_CC_TEMPLATE";
select_compile_drv_template("$CXX") -> "DRV_CXX_TEMPLATE".
select_compile_exe_template("$CC") -> "EXE_CC_TEMPLATE";
select_compile_exe_template("$CXX") -> "EXE_CXX_TEMPLATE".
needs_compile(Source, Bin) ->
needs_link(Bin, [Source|bin_deps(Bin)]).
%% NOTE: This relies on -MMD being passed to the compiler and returns an
%% empty list if the .d file is not available. This means header deps are
%% ignored on win32.
bin_deps(Bin) ->
[DepFile] = port_deps([Bin]),
case file:read_file(DepFile) of
{ok, Deps} ->
parse_bin_deps(list_to_binary(Bin), Deps);
{error, _Err} ->
[]
end.
parse_bin_deps(Bin, Deps) ->
Ds = re:split(Deps, "\\s*\\\\\\R\\s*|\\s+", [{return, binary}]),
[D || D <- Ds, D =/= <<>>, D =/= <<Bin/binary,":">>].
%%
%% == linking ==
%%
needs_link(SoName, []) ->
filelib:last_modified(SoName) == 0;
needs_link(SoName, NewBins) ->
MaxLastMod = lists:max([filelib:last_modified(B) || B <- NewBins]),
case filelib:last_modified(SoName) of
0 ->
true;
Other ->
MaxLastMod >= Other
end.
select_link_template(LinkLang, Target) ->
case {LinkLang, pc_util:target_type(Target)} of
{cc, drv} -> "DRV_LINK_TEMPLATE";
{cxx, drv} -> "DRV_LINK_CXX_TEMPLATE";
{cc, exe} -> "EXE_LINK_TEMPLATE";
{cxx, exe} -> "EXE_LINK_CXX_TEMPLATE"
end.