Skip to content

Commit

Permalink
Implement resource v2 callback compatibiity
Browse files Browse the repository at this point in the history
Implement rebar_resource_v2 compatible callbacks in order to make the plugin compatible with all known versions of rebar3, following the guide at https://www.rebar3.org/v3/docs/custom-dep-resources#section-resources-compatible-with-all-versions
  • Loading branch information
nalundgaard committed Dec 19, 2018
1 parent 1468599 commit ee9ea74
Showing 1 changed file with 154 additions and 21 deletions.
175 changes: 154 additions & 21 deletions src/rebar_raw_resource.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@
% provider
do/1,
format_error/1,
init/2,
% rebar_resource_v2 callbacks
download/4,
lock/2,
make_vsn/2,
needs_update/2,
% pre-rebar3 3.7.0 rebar_resource callbacks
init/1,
% rebar_resource
download/3,
lock/2,
make_vsn/1,
needs_update/2
make_vsn/1
]).

% For development only - you *really* don't want this defined!
Expand Down Expand Up @@ -194,10 +198,28 @@
% download/3 is NOT called first if the dependency is already present, and
% it's the only resource call that gets to see the rebar state.
%
% Note: This arity is the pre-rebar3 3.7.0 format of this function.
%
init(State) ->
#mod_data{} = absorb_state(State),
{'ok', rebar_state:add_resource(State, {?RTYPE, ?MODULE})}.

-spec init(Type :: rsrc_type(),
State :: rebar_state()) -> {'ok', rebar_state()}.
%
% Installs the resource handler, the provider itself does nothing.
%
% This gets called repeatedly, for each profile, and in each case we want
% to prime the process environment with any info we may need later, as
% download/4 is NOT called first if the dependency is already present, and
% it's the only resource call that gets to see the rebar state.
%
% Note: This arity is the rebar_resource_v2 format of this function.
%
init(Type, State) ->
#mod_data{} = absorb_state(State),
{'ok', rebar_resource_v2:new(Type, ?MODULE, State)}.

-spec do(State :: rebar_state:t()) -> {'ok', rebar_state()}.
%
% Fulfills the `provider' contract, does nothing ... for now.
Expand Down Expand Up @@ -225,14 +247,18 @@ format_error(Error) ->
%% Resource API
%% ===================================================================

-spec download(
Dest :: rsrc_dir(), From :: this_spec(), State :: rebar_state())
-> {'ok', term()} | rebar_err().
-spec download(Dest :: rsrc_dir(),
From :: this_spec(),
State :: rebar_state())
-> {'ok', term()} | rebar_err().
%
% Download the specified resource using its underlying handler.
%
download(Dest, {?RTYPE, Loc,
#mod_ref{res = Res, ref = Ref, opt = Opts}}, State) ->
% Note: This arity is the pre-rebar3 3.7.0 format of this function.
%
download(Dest,
{?RTYPE, Loc, #mod_ref{res = Res, ref = Ref, opt = Opts}},
State) ->
download(Dest, {?RTYPE, {Res, Loc, Ref}, Opts}, State);

download(Dest, {?RTYPE, Spec}, State) ->
Expand All @@ -254,13 +280,60 @@ download(Dest, {?RTYPE, Spec, Opts}, State) ->
Err
end.

-spec lock(Path :: rsrc_dir(), Spec :: this_spec())
-> rebar_lock() | no_return().
-spec download(Dest :: rsrc_dir(),
AppInfo :: rebar_app_info:t(),
ResourceState :: term(),
State :: rebar_state())
-> {'ok', term()} | rebar_err().
%
% Download the specified resource using its underlying handler.
%
% Note: This arity is the rebar_resource_v2 format of this function.
%
download(Dest, AppInfo0, ResourceState, RebarState) ->
{Spec, Opts} = case rebar_app_info:source(AppInfo0) of
{?RTYPE, Loc0, #mod_ref{res = Res0, ref = Ref, opt = Opts0}} ->
{{Res0, Loc0, Ref}, Opts0};
{?RTYPE, S, O} ->
{S, O};
{?RTYPE, S} ->
{S, []}
end,
{Res, Loc} = parse_ext_spec(Spec),
#mod_dep{name = Name} = lookup_loc(mod_data(), Loc),
#mod_res{mod = Mod} = lookup_res(mod_data(), Res),
AppInfo = rebar_app_info:source(AppInfo0, Spec),
case Mod:download(Dest, AppInfo, ResourceState, RebarState) of
ok ->
case lists:keytake(vsn, 1, Opts) of
false ->
% because download has not put the downloaded app into
% its final location, configure the app dir to be the
% Dest provided.
TmpAppInfo = rebar_app_info:dir(AppInfo, Dest),
{plain, Vsn} = Mod:make_vsn(TmpAppInfo, []),
ensure_app(Dest, Mod, Name, [{vsn, Vsn} |Opts], ok);
{value, _, _} ->
ensure_app(Dest, Mod, Name, Opts, ok)
end;
Err -> Err
end.

-spec lock(Path :: rsrc_dir(),
Spec :: this_spec())
-> rebar_lock() | no_return();
(AppInfo :: rebar_app_info:t(),
ResourceState :: term())
-> rebar_lock() | no_return().
%
% Pass through to the underlying resource handler.
% Note that the callback doesn't allow an error tuple to be returned, so an
% exception is our only option if we can't look up the mapping.
%
% Note: this function is common to the pre-rebar3 3.7.0 and rebar_resource_v2
% formats, and as such has heads for each.
%
%% pre-rebar3 3.7.0 format
lock(Path, {?RTYPE, Loc, #mod_ref{res = Res, ref = Prev} = Rec}) ->
#mod_res{mod = Mod} = lookup_res(mod_data(), Res),
{Res, Loc, Ref} = Mod:lock(Path, {Res, Loc, Prev}),
Expand All @@ -273,15 +346,35 @@ lock(Path, {?RTYPE, Spec, Opts}) ->
{Res, _} = parse_ext_spec(Spec),
#mod_res{mod = Mod} = lookup_res(mod_data(), Res),
{Res, Loc, Ref} = Mod:lock(Path, Spec),
{?RTYPE, Loc, #mod_ref{res = Res, ref = Ref, opt = Opts}};
%% rebar_resource_v2 format
lock(AppInfo, _ResourceState) ->
{Spec, Opts} = case rebar_app_info:source(AppInfo) of
{?RTYPE, Loc0, #mod_ref{res = Res0, ref = Ref0, opt = Opts0}} ->
{{Res0, Loc0, Ref0}, Opts0};
{?RTYPE, S, O} -> {S, O};
{?RTYPE, S} -> {S, []}
end,
{Res, _Loc} = parse_ext_spec(Spec),
#mod_res{mod = Mod} = lookup_res(mod_data(), Res),
{Res, Loc, Ref} = Mod:lock(rebar_app_info:source(AppInfo, Spec), []),
{?RTYPE, Loc, #mod_ref{res = Res, ref = Ref, opt = Opts}}.

-spec needs_update(Path :: rsrc_dir(), Spec :: this_spec())
-> boolean() | no_return().
-spec needs_update(Path :: rsrc_dir(),
SpecOrResourceState :: this_spec())
-> boolean() | no_return();
(AppInfo :: rebar_app_info:t(),
State :: rebar_state())
-> boolean() | no_return().
%
% Pass through to the underlying resource handler.
% Note that the callback doesn't allow an error tuple to be returned, so an
% exception is our only option if we can't look up the mapping.
%
% Note: this function is common to the pre-rebar3 3.7.0 and rebar_resource_v2
% formats, and as such has heads for each.
%
%% pre-rebar3 3.7.0 format
needs_update(Path, {?RTYPE, Loc, #mod_ref{res = Res, ref = Ref}}) ->
#mod_res{mod = Mod} = lookup_res(mod_data(), Res),
Mod:needs_update(Path, {Res, Loc, Ref});
Expand All @@ -292,20 +385,55 @@ needs_update(Path, {?RTYPE, Spec, _}) ->
needs_update(Path, {?RTYPE, Spec}) ->
{Res, _} = parse_ext_spec(Spec),
#mod_res{mod = Mod} = lookup_res(mod_data(), Res),
Mod:needs_update(Path, Spec).
Mod:needs_update(Path, Spec);
%% rebar_resource_v2 format
needs_update(AppInfo, State) ->
{Mod, SourceSpec} = case rebar_app_info:source(AppInfo) of
{?RTYPE, Loc, #mod_ref{res = Res, ref = Ref}} ->
#mod_res{mod = M} = lookup_res(mod_data(), Res),
{M, {Res, Loc, Ref}};
{?RTYPE, Spec, _} ->
{Res, _} = parse_ext_spec(Spec),
#mod_res{mod = M} = lookup_res(mod_data(), Res),
{M, Spec};
{?RTYPE, Spec} ->
{Res, _} = parse_ext_spec(Spec),
#mod_res{mod = M} = lookup_res(mod_data(), Res),
{M, Spec}
end,
Mod:needs_update(rebar_app_info:source(AppInfo, SourceSpec), State).

-spec make_vsn(Path :: rsrc_dir())
-> rebar_vsn() | {'error', string()} | no_return().
%
% Pass through to the underlying resource handler.
% The weird error tuple spec comes from the rebar_resource behavior.
% Note: This arity is the pre-rebar3 3.7.0 format of this function.
%
make_vsn(Path) ->
Data = mod_data(),
#mod_dep{res = Res} = lookup_dep(Data, path_name(Path)),
#mod_res{mod = Mod} = lookup_res(Data, Res),
Mod:make_vsn(Path).

-spec make_vsn(Path :: rsrc_dir(),
State :: rebar_state())
-> rebar_vsn() | {'error', string()} | no_return().
%
% Pass through to the underlying resource handler.
% The weird error tuple spec comes from the rebar_resource behavior.
% Note: This arity is the rebar_resource_v2 format of this function.
%
make_vsn(AppInfo0, _ResourceState) ->
Spec = case rebar_app_info:source(AppInfo0) of
{?RTYPE, Loc0, #mod_ref{res = Res0, ref = Ref}} -> {Res0, Loc0, Ref};
{?RTYPE, S, _} -> S;
{?RTYPE, S} -> S
end,
{Res, _Loc} = parse_ext_spec(Spec),
#mod_res{mod = Mod} = lookup_res(mod_data(), Res),
Mod:make_vsn(rebar_app_info:source(AppInfo0, Spec), []).

%% ===================================================================
%% Internal
%% ===================================================================
Expand Down Expand Up @@ -348,16 +476,21 @@ format_error(Mod, Func, Class, Data) ->
absorb_state(State) ->
Data1 = mod_data(),
Data2 = absorb_resources(Data1, rebar_state:resources(State)),
Data3 = absorb_deps(Data2, rebar_state:get(State, 'deps', [])),
Data4 = absorb_app_infos(Data3, rebar_state:lock(State)),
Data3 = absorb_app_infos(Data2, rebar_state:lock(State)),
Data4 = absorb_deps(Data3, rebar_state:get(State, 'deps', [])),
% Data5 = absorb_profiles(Data4, rebar_state:current_profiles(State), State),
mod_data(dump_mod_data(Data4)).

-spec absorb_app_infos(Data :: mod_data(), AppInfos :: [tuple()])
-> mod_data() | no_return().
absorb_app_infos(Data, [AppInfo | AppInfos]) ->
% absorb deps from app_info to ensure that all deps are absorbed
% see comments in https://github.com/alertlogic/rebar_raw_resource/pull/1
% for further discussion.
AppInfoDeps = rebar_app_info:get(AppInfo, deps, []),
absorb_app_infos(
absorb_dep(Data,
absorb_dep(
absorb_deps(Data, AppInfoDeps),
rebar_app_info:name(AppInfo), rebar_app_info:source(AppInfo)),
AppInfos);
absorb_app_infos(Data, []) ->
Expand Down Expand Up @@ -390,14 +523,14 @@ absorb_profiles(Data, [], _) ->
%
% Allow for whatever may come through to handle future extensions.
%
% rebar v2 resource
% rebar_resource_v2 format
absorb_resources(Data, [Res | Resources]) when ?is_rec_type(Res, resource, 3) ->
absorb_resources(
map_res(Data, #mod_res{
res = term_to_atom(erlang:element(2, Res)),
mod = erlang:element(3, Res)}),
Resources);
% old style rebar resource
% pre-rebar3 3.7.0 format
absorb_resources(Data, [Res | Resources]) when ?is_min_tuple(Res, 2) ->
absorb_resources(
map_res(Data, #mod_res{
Expand Down Expand Up @@ -480,9 +613,9 @@ ensure_app(Path, Mod, Name, Opts, Result) ->
" {applications, [kernel, stdlib]}\n"
"]}.\n",
[?MODULE, Name, Desc, Vsn]),
case filelib:ensure_dir(BApp) of
case filelib:ensure_dir(SApp) of
'ok' ->
case file:write_file(BApp, Data) of
case file:write_file(SApp, Data) of
'ok' ->
Result;
Err ->
Expand Down

0 comments on commit ee9ea74

Please sign in to comment.