Current section

Files

Jump to
pc src pc_port_specs.erl
Raw

src/pc_port_specs.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_port_specs).
-export([
construct/1,
%% spec accessors
environment/1,
objects/1,
sources/1,
target/1,
type/1,
link_lang/1
]).
-export_type([spec/0]).
-record(spec, {type::'drv' | 'exe',
link_lang::'cc' | 'cxx',
target::file:filename(),
sources = [] :: [file:filename(), ...],
objects = [] :: [file:filename(), ...],
opts = [] :: list() | []}).
-opaque spec() :: #spec{}.
%%%===================================================================
%%% API
%%%===================================================================
-spec construct(rebar_state:t()) -> {ok, [spec()]} |
{error, Reason :: any()}.
construct(State) ->
case rebar_state:get(State, port_specs, []) of
[] ->
{ok, [port_spec_from_legacy(State)]};
PortSpecs ->
Filtered = lists:filter(fun filter_port_spec/1, PortSpecs),
Specs = [get_port_spec(State, os:type(), Spec) || Spec <- Filtered],
{ok, [S || S <- Specs, S#spec.sources /= []]}
end.
%% == Spec Accessors ==
environment(#spec{opts = Opts}) -> proplists:get_value(env, Opts).
objects(#spec{objects = Objects}) -> Objects.
sources(#spec{sources = Sources}) -> Sources.
target(#spec{target = Target}) -> Target.
type(#spec{type = Type}) -> Type.
link_lang(#spec{link_lang = LinkLang}) -> LinkLang.
%%%===================================================================
%%% Internal Functions
%%%===================================================================
port_spec_from_legacy(Config) ->
%% Get the target from the so_name variable
Target = case rebar_state:get(Config, so_name, undefined) of
undefined ->
%% Generate a sensible default from app file
AppName = case rebar_state:current_app(Config) of
undefined ->
throw({error, {pc_prv_compile, no_app}});
CurrentApp ->
BAppName = rebar_app_info:name(CurrentApp),
binary_to_list(BAppName)
end,
filename:join("priv", lists:concat([AppName, "_drv.so"]));
AName ->
filename:join("priv", AName)
end,
%% Get the list of source files from port_sources
Sources = port_sources(rebar_state:dir(Config), rebar_state:get(Config, port_sources, ["c_src/*.c"])),
#spec { type = pc_util:target_type(Target),
link_lang = cc,
target = maybe_switch_extension(os:type(), Target),
sources = Sources,
objects = port_objects(Sources),
opts = [port_opt(Config, O) || O <- fill_in_defaults([])]}.
port_sources(WorkDir, Sources) ->
lists:flatmap(fun (Source) ->
filelib:wildcard(Source, WorkDir)
end, Sources).
maybe_switch_extension({win32, nt}, Target) ->
switch_to_dll_or_exe(Target);
maybe_switch_extension(_OsType, Target) ->
Target.
port_objects(SourceFiles) ->
[pc_util:replace_extension(O, ".o") || O <- SourceFiles].
filter_port_spec({ArchRegex, _, _, _}) ->
pc_util:is_arch(ArchRegex);
filter_port_spec({ArchRegex, _, _}) ->
pc_util:is_arch(ArchRegex);
filter_port_spec({_, _}) ->
true.
get_port_spec(Config, OsType, {Target, Sources}) ->
get_port_spec(Config, OsType, {undefined, Target, Sources, []});
get_port_spec(Config, OsType, {Arch, Target, Sources}) ->
get_port_spec(Config, OsType, {Arch, Target, Sources, []});
get_port_spec(Config, OsType, {_Arch, Target, Sources, Opts}) ->
SourceFiles = lists:flatmap(fun(Source) ->
Source1 = filename:join(rebar_state:dir(Config), Source),
filelib:wildcard(Source1)
end, Sources),
LinkLang =
case lists:any(
fun(Src) ->
pc_compilation:compiler(filename:extension(Src)) == "$CXX"
end,
SourceFiles)
of
true -> cxx;
false -> cc
end,
Target1 = filename:join(rebar_state:dir(Config), Target),
ObjectFiles = [pc_util:replace_extension(O, ".o") || O <- SourceFiles],
#spec{type = pc_util:target_type(Target1),
target = coerce_extension(OsType, Target1),
link_lang = LinkLang,
sources = SourceFiles,
objects = ObjectFiles,
opts = [port_opt(Config, O) || O <- fill_in_defaults(Opts)]}.
coerce_extension({win32, nt}, Target) ->
switch_to_dll_or_exe(Target);
coerce_extension(_OsType, Target) ->
Target.
switch_to_dll_or_exe(Target) ->
case filename:extension(Target) of
".so" -> filename:rootname(Target, ".so") ++ ".dll";
[] -> Target ++ ".exe";
_Other -> Target
end.
%% NOTE: By ensuring that the options list has default values we can simplify
%% the implementation of the compilation module. More invariants means less
%% lookups and/or branching.
%%
%% It might make sense to move 'env' into the top-level of the record. That's an
%% option too.
fill_in_defaults(Opts) ->
%% insert an {env, []} if {env, _} does not exist
case lists:any(fun ({env, _}) -> true;
(_) -> false end, Opts) of
true ->
Opts;
false ->
[{env, []} | Opts]
end.
port_opt(State, {env, Env}) ->
{ok, PortEnv} = pc_port_env:construct(State, Env),
{env, PortEnv};
port_opt(_State, Opt) ->
Opt.