summaryrefslogtreecommitdiff
path: root/src/rebar_packages.erl
blob: e21f1fd3bfa8811fc2dd7fa98c155f10cf345db8 (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
-module(rebar_packages).

-export([get_packages/1
        ,registry/1
        ,package_dir/1
        ,check_registry/3
        ,registry_checksum/2
        ,find_highest_matching/3]).

-export_type([package/0]).

-include("rebar.hrl").

-type pkg_name() :: binary() | atom().
-type vsn() :: binary().
-type package() :: pkg_name() | {pkg_name(), vsn()}.

-spec get_packages(rebar_state:t()) -> {rebar_dict(), rebar_digraph()}.
get_packages(State) ->
    RegistryDir = package_dir(State),
    DictFile = filename:join(RegistryDir, "dict"),
    Edges = filename:join(RegistryDir, "edges"),
    Vertices = filename:join(RegistryDir, "vertices"),
    Neighbors = filename:join(RegistryDir, "neighbors"),

    case lists:all(fun(X) -> filelib:is_file(X) end, [DictFile, Edges, Vertices, Neighbors]) of
        true ->
            try
                {ok, DictBinary} = file:read_file(DictFile),
                Dict = binary_to_term(DictBinary),
                {ok, EdgesTab} = ets:file2tab(Edges),
                {ok, VerticesTab} = ets:file2tab(Vertices),
                {ok, NeighborsTab} = ets:file2tab(Neighbors),
                {Dict, {digraph, EdgesTab, VerticesTab, NeighborsTab, true}}
            catch
                _:_ ->
                    ?ERROR("Bad packages index, try to fix with `rebar3 update`", []),
                    {dict:new(), digraph:new()}
            end;
        false ->
            ?ERROR("Bad packages index, try to fix with `rebar3 update`", []),
            {dict:new(), digraph:new()}
    end.

registry(State) ->
    RegistryDir = package_dir(State),
    HexFile = filename:join(RegistryDir, "registry"),
    case ets:file2tab(HexFile) of
        {ok, T} ->
            {ok, T};
        {error, Reason} ->
            ?DEBUG("Error loading registry: ~p", [Reason]),
            error
    end.

package_dir(State) ->
    CacheDir = rebar_dir:global_cache_dir(State),
    CDN = rebar_state:get(State, rebar_packages_cdn, ?DEFAULT_CDN),
    {ok, {_, _, Host, _, Path, _}} = http_uri:parse(CDN),
    CDNHostPath = lists:reverse(string:tokens(Host, ".")),
    CDNPath = tl(filename:split(Path)),
    PackageDir = filename:join([CacheDir, "hex"] ++ CDNHostPath ++ CDNPath ++ ["packages"]),
    ok = filelib:ensure_dir(filename:join(PackageDir, "placeholder")),
    PackageDir.


check_registry(Pkg, Vsn, State) ->
    case rebar_state:registry(State) of
        {ok, T} ->
            case ets:lookup(T, Pkg) of
                [{Pkg, [Vsns]}] ->
                    lists:member(Vsn, Vsns);
                _ ->
                    false
            end;
        error ->
            false
    end.

registry_checksum({pkg, Name, Vsn}, State) ->
    {ok, Registry} = registry(State),
    case ets:lookup(Registry, {Name, Vsn}) of
        [{{_, _}, [_, Checksum | _]}] ->
            Checksum;
        [] ->
            none
    end.

%% Hex supports use of ~> to specify the version required for a dependency.
%% Since rebar3 requires exact versions to choose from we find the highest
%% available version of the dep that passes the constraint.

%% `~>` will never include pre-release versions of its upper bound.
%% It can also be used to set an upper bound on only the major
%% version part. See the table below for `~>` requirements and
%% their corresponding translation.
%% `~>` | Translation
%% :------------- | :---------------------
%% `~> 2.0.0` | `>= 2.0.0 and < 2.1.0`
%% `~> 2.1.2` | `>= 2.1.2 and < 2.2.0`
%% `~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
%% `~> 2.0` | `>= 2.0.0 and < 3.0.0`
%% `~> 2.1` | `>= 2.1.0 and < 3.0.0`
find_highest_matching(Dep, Constraint, T) ->
    case ets:lookup(T, Dep) of
        [{Dep, [[Vsn]]}] ->
            case ec_semver:pes(Vsn, Constraint) of
                true ->
                    {ok, Vsn};
                false ->
                    ?WARN("Only existing version of ~s is ~s which does not match constraint ~~> ~s. "
                         "Using anyway, but it is not guarenteed to work.", [Dep, Vsn, Constraint]),
                    {ok, Vsn}
            end;
        [{Dep, [[HeadVsn | VsnTail]]}] ->
            {ok, lists:foldl(fun(Version, Highest) ->
                                case ec_semver:pes(Version, Constraint) andalso
                                    ec_semver:gt(Version, Highest) of
                                    true ->
                                        Version;
                                    false ->
                                        Highest
                                end
                        end, HeadVsn, VsnTail)};
        [] ->
            ?WARN("Missing registry entry for package ~s", [Dep]),
            none
    end.