summaryrefslogtreecommitdiff
path: root/src/rebar_resource_v2.erl
blob: f032f6e407d3119d26ba11c6732b3fb9e1e50df6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ts=4 sw=4 et
-module(rebar_resource_v2).

-export([new/3,
         find_resource_state/2,
         format_source/1,
         lock/2,
         download/3,
         needs_update/2,
         make_vsn/3,
         format_error/1]).

-export_type([resource/0,
              source/0,
              type/0,
              location/0,
              ref/0,
              resource_state/0]).

-include("rebar.hrl").
-include_lib("providers/include/providers.hrl").

-type resource() :: #resource{}.
-type source() :: {type(), location(), ref()} | {type(), location(), ref(), binary()}.
-type type() :: atom().
-type location() :: string().
-type ref() :: any().
-type resource_state() :: term().

-callback init(type(), rebar_state:t()) -> {ok, resource()}.
-callback lock(rebar_app_info:t(), resource_state()) -> source().
-callback download(file:filename_all(), rebar_app_info:t(), resource_state(), rebar_state:t()) ->
    ok | {error, any()}.
-callback needs_update(rebar_app_info:t(), resource_state()) -> boolean().
-callback make_vsn(rebar_app_info:t(), resource_state()) ->
    {plain, string()} | {error, string()}.

-spec new(type(), module(), term()) -> resource().
new(Type, Module, State) ->
    #resource{type=Type,
              module=Module,
              state=State,
              implementation=?MODULE}.

-spec find_resource(type(), [resource()]) -> {ok, resource()} | {error, not_found}.
find_resource(Type, Resources) ->
    case ec_lists:find(fun(#resource{type=T}) -> T =:= Type end, Resources) of
        error when is_atom(Type) ->
            case code:which(Type) of
                non_existing ->
                    {error, not_found};
                _ ->
                    {ok, rebar_resource:new(Type, Type, #{})}
            end;
        error ->
            {error, not_found};
        {ok, Resource} ->
            {ok, Resource}
    end.

find_resource_state(Type, Resources) ->
    case lists:keyfind(Type, #resource.type, Resources) of
        false ->            
            {error, not_found};
        #resource{state=State} ->
            State
    end.

format_source({pkg, Name, Vsn, _Hash, _}) -> {pkg, Name, Vsn};
format_source(Source) -> Source.

lock(AppInfo, State) ->
    resource_run(lock, rebar_app_info:source(AppInfo), [AppInfo], State).

resource_run(Function, Source, Args, State) ->
    Resources = rebar_state:resources(State),
    case get_resource_type(Source, Resources) of
        {ok, #resource{type=_,
                       module=Module,
                       state=ResourceState,
                       implementation=?MODULE}} ->
            erlang:apply(Module, Function, Args++[ResourceState]);
        {ok, #resource{type=_,
                       module=Module,
                       state=_,
                       implementation=rebar_resource}} ->
            erlang:apply(rebar_resource, Function, [Module | Args])
    end.

download(TmpDir, AppInfo, State) ->
    resource_run(download, rebar_app_info:source(AppInfo), [TmpDir, AppInfo, State], State).

needs_update(AppInfo, State) ->
    resource_run(needs_update, rebar_app_info:source(AppInfo), [AppInfo], State).

%% this is a special case since it is used for project apps as well, not just deps
make_vsn(AppInfo, VcsType, State) ->
    Resources = rebar_state:resources(State),
    case is_resource_type(VcsType, Resources) of
        true ->
            case find_resource(VcsType, Resources) of
                {ok, #resource{type=_,
                               module=Module,
                               state=ResourceState,
                               implementation=?MODULE}} ->
                    Module:make_vsn(AppInfo, ResourceState);
                {ok, #resource{type=_,
                               module=Module,
                               state=_,
                               implementation=rebar_resource}} ->
                    rebar_resource:make_vsn(Module, AppInfo)
            end;
        false ->
            unknown
    end.

format_error({no_resource, Location, Type}) ->
    io_lib:format("Cannot handle dependency ~ts.~n"
                  "     No module found for resource type ~p.", [Location, Type]);
format_error({no_resource, Source}) ->
    io_lib:format("Cannot handle dependency ~ts.~n"
                  "     No module found for unknown resource type.", [Source]).

is_resource_type(Type, Resources) ->
    lists:any(fun(#resource{type=T}) -> T =:= Type end, Resources).

-spec get_resource_type(term(), [resource()]) -> {ok, resource()}.
get_resource_type({Type, Location}, Resources) ->
    get_resource(Type, Location, Resources);
get_resource_type({Type, Location, _}, Resources) ->
    get_resource(Type, Location, Resources);
get_resource_type({Type, _, _, Location}, Resources) ->
    get_resource(Type, Location, Resources);
get_resource_type(Location={Type, _, _, _, _}, Resources) ->
    get_resource(Type, Location, Resources);
get_resource_type(Source, _) ->
    throw(?PRV_ERROR({no_resource, Source})).

-spec get_resource(type(), term(), [resource()]) -> {ok, resource()}.
get_resource(Type, Location, Resources) ->
    case find_resource(Type, Resources) of
        {error, not_found} ->
            throw(?PRV_ERROR({no_resource, Location, Type}));
        {ok, Resource} ->
            {ok, Resource}
    end.