-module(rebar_app_info).
|
|
|
|
-export([new/0,
|
|
new/1,
|
|
new/2,
|
|
new/3,
|
|
new/4,
|
|
new/5,
|
|
update_opts/3,
|
|
discover/1,
|
|
name/1,
|
|
name/2,
|
|
app_file_src/1,
|
|
app_file_src/2,
|
|
app_file_src_script/1,
|
|
app_file_src_script/2,
|
|
app_file/1,
|
|
app_file/2,
|
|
app_details/1,
|
|
app_details/2,
|
|
parent/1,
|
|
parent/2,
|
|
original_vsn/1,
|
|
original_vsn/2,
|
|
ebin_dir/1,
|
|
applications/1,
|
|
applications/2,
|
|
profiles/1,
|
|
profiles/2,
|
|
deps/1,
|
|
deps/2,
|
|
dep_level/1,
|
|
dep_level/2,
|
|
dir/1,
|
|
dir/2,
|
|
out_dir/1,
|
|
out_dir/2,
|
|
default/1,
|
|
default/2,
|
|
opts/1,
|
|
opts/2,
|
|
get/2,
|
|
get/3,
|
|
set/3,
|
|
resource_type/1,
|
|
resource_type/2,
|
|
source/1,
|
|
source/2,
|
|
is_lock/1,
|
|
is_lock/2,
|
|
is_checkout/1,
|
|
is_checkout/2,
|
|
valid/1,
|
|
valid/2,
|
|
|
|
has_all_artifacts/1,
|
|
|
|
apply_overrides/2,
|
|
add_to_profile/3,
|
|
apply_profiles/2,
|
|
deduplicate/1,
|
|
do_deduplicate/2]).
|
|
|
|
-include("rebar.hrl").
|
|
-include_lib("providers/include/providers.hrl").
|
|
|
|
-export_type([t/0]).
|
|
|
|
-record(app_info_t, {name :: binary(),
|
|
app_file_src :: file:filename_all() | undefined,
|
|
app_file_src_script:: file:filename_all() | undefined,
|
|
app_file :: file:filename_all() | undefined,
|
|
original_vsn :: binary() | string() | undefined,
|
|
parent=root :: binary() | root,
|
|
app_details=[] :: list(),
|
|
applications=[] :: list(),
|
|
deps=[] :: list(),
|
|
profiles=[default] :: [atom()],
|
|
default=dict:new() :: rebar_dict(),
|
|
opts=dict:new() :: rebar_dict(),
|
|
dep_level=0 :: integer(),
|
|
dir :: file:name(),
|
|
out_dir :: file:name(),
|
|
resource_type :: pkg | src,
|
|
source :: string() | tuple() | undefined,
|
|
is_lock=false :: boolean(),
|
|
is_checkout=false :: boolean(),
|
|
valid :: boolean()}).
|
|
|
|
%%============================================================================
|
|
%% types
|
|
%%============================================================================
|
|
-type t() :: #app_info_t{}.
|
|
|
|
%%============================================================================
|
|
%% API
|
|
%% ============================================================================
|
|
%% @doc Build a new, empty, app info value. This is not of a lot of use and you
|
|
%% probably wont be doing this much.
|
|
-spec new() -> t().
|
|
new() ->
|
|
#app_info_t{}.
|
|
|
|
-spec new(atom() | binary() | string()) ->
|
|
{ok, t()}.
|
|
new(AppName) ->
|
|
{ok, #app_info_t{name=ec_cnv:to_binary(AppName)}}.
|
|
|
|
-spec new(atom() | binary() | string(), binary() | string()) ->
|
|
{ok, t()}.
|
|
new(AppName, Vsn) ->
|
|
{ok, #app_info_t{name=ec_cnv:to_binary(AppName),
|
|
original_vsn=Vsn}}.
|
|
|
|
%% @doc build a complete version of the app info with all fields set.
|
|
-spec new(atom() | binary() | string(), binary() | string(), file:name()) ->
|
|
{ok, t()}.
|
|
new(AppName, Vsn, Dir) ->
|
|
{ok, #app_info_t{name=ec_cnv:to_binary(AppName),
|
|
original_vsn=Vsn,
|
|
dir=ec_cnv:to_list(Dir),
|
|
out_dir=ec_cnv:to_list(Dir)}}.
|
|
|
|
%% @doc build a complete version of the app info with all fields set.
|
|
-spec new(atom() | binary() | string(), binary() | string(), file:name(), list()) ->
|
|
{ok, t()}.
|
|
new(AppName, Vsn, Dir, Deps) ->
|
|
{ok, #app_info_t{name=ec_cnv:to_binary(AppName),
|
|
original_vsn=Vsn,
|
|
dir=ec_cnv:to_list(Dir),
|
|
out_dir=ec_cnv:to_list(Dir),
|
|
deps=Deps}}.
|
|
|
|
%% @doc build a complete version of the app info with all fields set.
|
|
-spec new(atom() | binary(), atom() | binary() | string(), binary() | string(), file:name(), list()) ->
|
|
{ok, t()}.
|
|
new(Parent, AppName, Vsn, Dir, Deps) ->
|
|
{ok, #app_info_t{name=ec_cnv:to_binary(AppName),
|
|
parent=Parent,
|
|
original_vsn=Vsn,
|
|
dir=ec_cnv:to_list(Dir),
|
|
out_dir=ec_cnv:to_list(Dir),
|
|
deps=Deps}}.
|
|
|
|
update_opts(AppInfo, Opts, Config) ->
|
|
LockDeps = case resource_type(AppInfo) of
|
|
pkg ->
|
|
Deps = deps(AppInfo),
|
|
[{{locks, default}, Deps}, {{deps, default}, Deps}];
|
|
_ ->
|
|
deps_from_config(dir(AppInfo), Config)
|
|
end,
|
|
|
|
Plugins = proplists:get_value(plugins, Config, []),
|
|
Terms = LockDeps++[{{plugins, default}, Plugins} | Config],
|
|
true = rebar_config:verify_config_format(Terms),
|
|
LocalOpts = dict:from_list(Terms),
|
|
|
|
NewOpts = rebar_opts:merge_opts(LocalOpts, Opts),
|
|
|
|
AppInfo#app_info_t{opts=NewOpts
|
|
,default=NewOpts}.
|
|
|
|
deps_from_config(Dir, Config) ->
|
|
case rebar_config:consult_lock_file(filename:join(Dir, ?LOCK_FILE)) of
|
|
[D] ->
|
|
%% We want the top level deps only from the lock file.
|
|
%% This ensures deterministic overrides for configs.
|
|
Deps = [X || X <- D, element(3, X) =:= 0],
|
|
[{{locks, default}, D}, {{deps, default}, Deps}];
|
|
_ ->
|
|
[{{deps, default}, proplists:get_value(deps, Config, [])}]
|
|
end.
|
|
|
|
%% @doc discover a complete version of the app info with all fields set.
|
|
-spec discover(file:filename_all()) -> {ok, t()} | not_found.
|
|
discover(Dir) ->
|
|
case rebar_app_discover:find_app(Dir, all) of
|
|
{true, AppInfo} ->
|
|
{ok, AppInfo};
|
|
false ->
|
|
not_found
|
|
end.
|
|
|
|
-spec name(t()) -> binary().
|
|
name(#app_info_t{name=Name}) ->
|
|
Name.
|
|
|
|
-spec name(t(), atom() | binary() | string()) -> t().
|
|
name(AppInfo=#app_info_t{}, AppName) ->
|
|
AppInfo#app_info_t{name=ec_cnv:to_binary(AppName)}.
|
|
|
|
opts(#app_info_t{opts=Opts}) ->
|
|
Opts.
|
|
|
|
opts(AppInfo, Opts) ->
|
|
AppInfo#app_info_t{opts=Opts}.
|
|
|
|
default(#app_info_t{default=Default}) ->
|
|
Default.
|
|
|
|
default(AppInfo, Default) ->
|
|
AppInfo#app_info_t{default=Default}.
|
|
|
|
get(AppInfo, Key) ->
|
|
{ok, Value} = dict:find(Key, AppInfo#app_info_t.opts),
|
|
Value.
|
|
|
|
get(AppInfo, Key, Default) ->
|
|
case dict:find(Key, AppInfo#app_info_t.opts) of
|
|
{ok, Value} ->
|
|
Value;
|
|
error ->
|
|
Default
|
|
end.
|
|
|
|
-spec set(t(), any(), any()) -> t().
|
|
set(AppInfo=#app_info_t{opts=Opts}, Key, Value) ->
|
|
AppInfo#app_info_t{opts = dict:store(Key, Value, Opts)}.
|
|
|
|
-spec app_file_src(t()) -> file:filename_all() | undefined.
|
|
app_file_src(#app_info_t{app_file_src=undefined, dir=Dir, name=Name}) ->
|
|
AppFileSrc = filename:join([ec_cnv:to_list(Dir), "src", ec_cnv:to_list(Name)++".app.src"]),
|
|
case filelib:is_file(AppFileSrc) of
|
|
true ->
|
|
AppFileSrc;
|
|
false ->
|
|
undefined
|
|
end;
|
|
app_file_src(#app_info_t{app_file_src=AppFileSrc}) ->
|
|
ec_cnv:to_list(AppFileSrc).
|
|
|
|
-spec app_file_src(t(), file:filename_all()) -> t().
|
|
app_file_src(AppInfo=#app_info_t{}, AppFileSrc) ->
|
|
AppInfo#app_info_t{app_file_src=ec_cnv:to_list(AppFileSrc)}.
|
|
|
|
-spec app_file_src_script(t()) -> file:filename_all() | undefined.
|
|
app_file_src_script(#app_info_t{app_file_src_script=undefined, dir=Dir, name=Name}) ->
|
|
AppFileSrcScript = filename:join([ec_cnv:to_list(Dir), "src", ec_cnv:to_list(Name)++".app.src.script"]),
|
|
case filelib:is_file(AppFileSrcScript) of
|
|
true ->
|
|
AppFileSrcScript;
|
|
false ->
|
|
undefined
|
|
end;
|
|
app_file_src_script(#app_info_t{app_file_src_script=AppFileSrcScript}) ->
|
|
ec_cnv:to_list(AppFileSrcScript).
|
|
|
|
-spec app_file_src_script(t(), file:filename_all()) -> t().
|
|
app_file_src_script(AppInfo=#app_info_t{}, AppFileSrcScript) ->
|
|
AppInfo#app_info_t{app_file_src_script=ec_cnv:to_list(AppFileSrcScript)}.
|
|
|
|
-spec app_file(t()) -> file:filename_all() | undefined.
|
|
app_file(#app_info_t{app_file=undefined, out_dir=Dir, name=Name}) ->
|
|
AppFile = filename:join([ec_cnv:to_list(Dir), "ebin", ec_cnv:to_list(Name)++".app"]),
|
|
case filelib:is_file(AppFile) of
|
|
true ->
|
|
AppFile;
|
|
false ->
|
|
undefined
|
|
end;
|
|
app_file(#app_info_t{app_file=AppFile}) ->
|
|
AppFile.
|
|
|
|
-spec app_file(t(), file:filename_all()) -> t().
|
|
app_file(AppInfo=#app_info_t{}, AppFile) ->
|
|
AppInfo#app_info_t{app_file=AppFile}.
|
|
|
|
-spec app_details(t()) -> list().
|
|
app_details(AppInfo=#app_info_t{app_details=[]}) ->
|
|
case app_file(AppInfo) of
|
|
undefined ->
|
|
rebar_file_utils:try_consult(app_file_src(AppInfo));
|
|
AppFile ->
|
|
try
|
|
rebar_file_utils:try_consult(AppFile)
|
|
catch
|
|
throw:{error, {Module, Reason}} ->
|
|
?DEBUG("Warning, falling back to .app.src because of: ~s",
|
|
[Module:format_error(Reason)]),
|
|
rebar_file_utils:try_consult(app_file_src(AppInfo))
|
|
end
|
|
end;
|
|
app_details(#app_info_t{app_details=AppDetails}) ->
|
|
AppDetails.
|
|
|
|
-spec app_details(t(), list()) -> t().
|
|
app_details(AppInfo=#app_info_t{}, AppDetails) ->
|
|
AppInfo#app_info_t{app_details=AppDetails}.
|
|
|
|
parent(#app_info_t{parent=Parent}) ->
|
|
Parent.
|
|
|
|
-spec parent(t(), binary() | root) -> t().
|
|
parent(AppInfo=#app_info_t{}, Parent) ->
|
|
AppInfo#app_info_t{parent=Parent}.
|
|
|
|
-spec original_vsn(t()) -> string().
|
|
original_vsn(#app_info_t{original_vsn=Vsn}) ->
|
|
Vsn.
|
|
|
|
-spec original_vsn(t(), string()) -> t().
|
|
original_vsn(AppInfo=#app_info_t{}, Vsn) ->
|
|
AppInfo#app_info_t{original_vsn=Vsn}.
|
|
|
|
-spec applications(t()) -> list().
|
|
applications(#app_info_t{applications=Applications}) ->
|
|
Applications.
|
|
|
|
-spec applications(t(), list()) -> t().
|
|
applications(AppInfo=#app_info_t{}, Applications) ->
|
|
AppInfo#app_info_t{applications=Applications}.
|
|
|
|
-spec profiles(t()) -> list().
|
|
profiles(#app_info_t{profiles=Profiles}) ->
|
|
Profiles.
|
|
|
|
-spec profiles(t(), list()) -> t().
|
|
profiles(AppInfo=#app_info_t{}, Profiles) ->
|
|
AppInfo#app_info_t{profiles=Profiles}.
|
|
|
|
-spec deps(t()) -> list().
|
|
deps(#app_info_t{deps=Deps}) ->
|
|
Deps.
|
|
|
|
-spec deps(t(), list()) -> t().
|
|
deps(AppInfo=#app_info_t{}, Deps) ->
|
|
AppInfo#app_info_t{deps=Deps}.
|
|
|
|
dep_level(AppInfo=#app_info_t{}, Level) ->
|
|
AppInfo#app_info_t{dep_level=Level}.
|
|
|
|
dep_level(#app_info_t{dep_level=Level}) ->
|
|
Level.
|
|
|
|
-spec dir(t()) -> file:name().
|
|
dir(#app_info_t{dir=Dir}) ->
|
|
Dir.
|
|
|
|
-spec dir(t(), file:name()) -> t().
|
|
dir(AppInfo=#app_info_t{out_dir=undefined}, Dir) ->
|
|
AppInfo#app_info_t{dir=ec_cnv:to_list(Dir),
|
|
out_dir=ec_cnv:to_list(Dir)};
|
|
dir(AppInfo=#app_info_t{}, Dir) ->
|
|
AppInfo#app_info_t{dir=ec_cnv:to_list(Dir)}.
|
|
|
|
-spec out_dir(t()) -> file:name().
|
|
out_dir(#app_info_t{out_dir=OutDir}) ->
|
|
OutDir.
|
|
|
|
-spec out_dir(t(), file:name()) -> t().
|
|
out_dir(AppInfo=#app_info_t{}, OutDir) ->
|
|
AppInfo#app_info_t{out_dir=ec_cnv:to_list(OutDir)}.
|
|
|
|
-spec ebin_dir(t()) -> file:name().
|
|
ebin_dir(#app_info_t{out_dir=OutDir}) ->
|
|
ec_cnv:to_list(filename:join(OutDir, "ebin")).
|
|
|
|
-spec resource_type(t(), pkg | src) -> t().
|
|
resource_type(AppInfo=#app_info_t{}, Type) ->
|
|
AppInfo#app_info_t{resource_type=Type}.
|
|
|
|
-spec resource_type(t()) -> pkg | src.
|
|
resource_type(#app_info_t{resource_type=ResourceType}) ->
|
|
ResourceType.
|
|
|
|
-spec source(t(), string() | tuple()) -> t().
|
|
source(AppInfo=#app_info_t{}, Source) ->
|
|
AppInfo#app_info_t{source=Source}.
|
|
|
|
-spec source(t()) -> string() | tuple().
|
|
source(#app_info_t{source=Source}) ->
|
|
Source.
|
|
|
|
-spec is_lock(t(), boolean()) -> t().
|
|
is_lock(AppInfo=#app_info_t{}, IsLock) ->
|
|
AppInfo#app_info_t{is_lock=IsLock}.
|
|
|
|
-spec is_lock(t()) -> boolean().
|
|
is_lock(#app_info_t{is_lock=IsLock}) ->
|
|
IsLock.
|
|
|
|
-spec is_checkout(t(), boolean()) -> t().
|
|
is_checkout(AppInfo=#app_info_t{}, IsCheckout) ->
|
|
AppInfo#app_info_t{is_checkout=IsCheckout}.
|
|
|
|
-spec is_checkout(t()) -> boolean().
|
|
is_checkout(#app_info_t{is_checkout=IsCheckout}) ->
|
|
IsCheckout.
|
|
|
|
-spec valid(t()) -> boolean().
|
|
valid(AppInfo=#app_info_t{valid=undefined}) ->
|
|
case rebar_app_utils:validate_application_info(AppInfo) =:= true
|
|
andalso has_all_artifacts(AppInfo) =:= true of
|
|
true ->
|
|
true;
|
|
_ ->
|
|
false
|
|
end;
|
|
valid(#app_info_t{valid=Valid}) ->
|
|
Valid.
|
|
|
|
-spec valid(t(), boolean()) -> t().
|
|
valid(AppInfo=#app_info_t{}, Valid) ->
|
|
AppInfo#app_info_t{valid=Valid}.
|
|
|
|
-spec has_all_artifacts(#app_info_t{}) -> true | {false, file:filename()}.
|
|
has_all_artifacts(AppInfo) ->
|
|
Artifacts = rebar_app_info:get(AppInfo, artifacts, []),
|
|
Dir = dir(AppInfo),
|
|
all(Dir, Artifacts).
|
|
|
|
all(_, []) ->
|
|
true;
|
|
all(Dir, [File|Artifacts]) ->
|
|
case filelib:is_regular(filename:join(Dir, File)) of
|
|
false ->
|
|
?DEBUG("Missing artifact ~s", [filename:join(Dir, File)]),
|
|
{false, File};
|
|
true ->
|
|
all(Dir, Artifacts)
|
|
end.
|
|
|
|
%%%%%
|
|
|
|
apply_overrides(Overrides, AppInfo) ->
|
|
Name = binary_to_atom(rebar_app_info:name(AppInfo), utf8),
|
|
Opts = rebar_opts:apply_overrides(opts(AppInfo), Name, Overrides),
|
|
AppInfo#app_info_t{default=Opts, opts=Opts}.
|
|
|
|
add_to_profile(AppInfo, Profile, KVs) when is_atom(Profile), is_list(KVs) ->
|
|
Opts = rebar_opts:add_to_profile(opts(AppInfo), Profile, KVs),
|
|
AppInfo#app_info_t{opts=Opts}.
|
|
|
|
apply_profiles(AppInfo, Profile) when not is_list(Profile) ->
|
|
apply_profiles(AppInfo, [Profile]);
|
|
apply_profiles(AppInfo, [default]) ->
|
|
AppInfo;
|
|
apply_profiles(AppInfo=#app_info_t{default = Defaults, profiles=CurrentProfiles}, Profiles) ->
|
|
AppliedProfiles = case Profiles of
|
|
%% Head of list global profile is special, only for use by rebar3
|
|
%% It does not clash if a user does `rebar3 as global...` but when
|
|
%% it is the head we must make sure not to prepend `default`
|
|
[global | _] ->
|
|
Profiles;
|
|
_ ->
|
|
deduplicate(CurrentProfiles ++ Profiles)
|
|
end,
|
|
|
|
ConfigProfiles = rebar_app_info:get(AppInfo, profiles, []),
|
|
|
|
NewOpts =
|
|
lists:foldl(fun(default, OptsAcc) ->
|
|
OptsAcc;
|
|
(Profile, OptsAcc) ->
|
|
case proplists:get_value(Profile, ConfigProfiles, []) of
|
|
OptsList when is_list(OptsList) ->
|
|
ProfileOpts = dict:from_list(OptsList),
|
|
rebar_opts:merge_opts(Profile, ProfileOpts, OptsAcc);
|
|
Other ->
|
|
throw(?PRV_ERROR({profile_not_list, Profile, Other}))
|
|
end
|
|
end, Defaults, AppliedProfiles),
|
|
AppInfo#app_info_t{profiles = AppliedProfiles, opts=NewOpts}.
|
|
|
|
deduplicate(Profiles) ->
|
|
do_deduplicate(lists:reverse(Profiles), []).
|
|
|
|
do_deduplicate([], Acc) ->
|
|
Acc;
|
|
do_deduplicate([Head | Rest], Acc) ->
|
|
case lists:member(Head, Acc) of
|
|
true -> do_deduplicate(Rest, Acc);
|
|
false -> do_deduplicate(Rest, [Head | Acc])
|
|
end.
|