summaryrefslogtreecommitdiff
path: root/src/rebar_fetch.erl
blob: 09e52a9f702c22e6d651f0b11ca2e40c59b3cf23 (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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ts=4 sw=4 et
%% -------------------------------------------------------------------
%%
%% rebar: Erlang Build Tools
%%
%% -------------------------------------------------------------------
-module(rebar_fetch).

-export([new/4,
         current_ref/2,
         download_source/2,
         update_source1/2,
         source_engine_avail/1,
         source_engine_avail/2,
         has_vcs_dir/2,
         print_source/1,
         format_source/2]).

-include("rebar.hrl").

-record(p4_settings, {
          client=undefined,
          transport="tcp4:perforce:1666",
          username,
          password
         }).

new(Dir, App, Vsn, Source) ->
    Ref = current_ref(Dir, Source),
    {App, Vsn, setelement(3, Source, Ref)}.

init_p4_settings(Basename) ->
    #p4_settings{client =
                     case inet:gethostname() of
                         {ok,HostName} ->
                             HostName ++ "-"
                                 ++ os:getenv("USER") ++ "-"
                                 ++ Basename
                                 ++ "-Rebar-automated-download"
                     end}.

current_ref(AppDir, {git, _, _}) ->
    string:strip(os:cmd("git --git-dir='" ++ AppDir ++ "/.git' rev-parse --verify HEAD"), both, $\n).

download_source(AppDir, Source) ->
    ec_file:mkdir_p(AppDir),
    TmpDir = ec_file:insecure_mkdtemp(),
    download_source_tmp(TmpDir, Source),
    ok = ec_file:copy(TmpDir, binary_to_list(filename:absname(AppDir)), [recursive]).

download_source_tmp(TmpDir, {p4, Url}) ->
    download_source_tmp(TmpDir, {p4, Url, "#head"});
download_source_tmp(TmpDir, {p4, Url, Rev}) ->
    download_source_tmp(TmpDir, {p4, Url, Rev, init_p4_settings(filename:basename(TmpDir))});
download_source_tmp(TmpDir, {p4, Url, _Rev, Settings}) ->
    ok = filelib:ensure_dir(TmpDir),
    rebar_utils:sh_send("p4 client -i",
                   ?FMT("Client: ~s~n"
                        ++"Description: generated by Rebar~n"
                        ++"Root: ~s~n"
                        ++"View:~n"
                        ++"  ~s/...  //~s/...~n",
                        [Settings#p4_settings.client,
                         TmpDir,
                         Url,
                         Settings#p4_settings.client]),
                       []),
    rebar_utils:sh(?FMT("p4 -c ~s sync -f", [Settings#p4_settings.client]), []);
download_source_tmp(TmpDir, {hg, Url, Rev}) ->
    ok = filelib:ensure_dir(TmpDir),
    rebar_utils:sh(?FMT("hg clone -U ~s ~s", [Url, filename:basename(TmpDir)]),
                   [{cd, filename:dirname(TmpDir)}]),
    rebar_utils:sh(?FMT("hg update ~s", [Rev]), [{cd, TmpDir}]);
download_source_tmp(TmpDir, {git, Url}) ->
    download_source_tmp(TmpDir, {git, Url, {branch, "HEAD"}});
download_source_tmp(TmpDir, {git, Url, ""}) ->
    download_source_tmp(TmpDir, {git, Url, {branch, "HEAD"}});
download_source_tmp(TmpDir, {git, Url, {branch, Branch}}) ->
    ok = filelib:ensure_dir(TmpDir),
    rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(TmpDir)]),
                   [{cd, filename:dirname(TmpDir)}]),
    rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), [{cd, TmpDir}]);
download_source_tmp(TmpDir, {git, Url, {tag, Tag}}) ->
    ok = filelib:ensure_dir(TmpDir),
    rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(TmpDir)]),
                   [{cd, filename:dirname(TmpDir)}]),
    rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), [{cd, TmpDir}]);
download_source_tmp(TmpDir, {git, Url, Rev}) ->
    ok = filelib:ensure_dir(TmpDir),
    rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(TmpDir)]),
                   [{cd, filename:dirname(TmpDir)}]),
    rebar_utils:sh(?FMT("git checkout -q ~s", [Rev]), [{cd, TmpDir}]);
download_source_tmp(TmpDir, {bzr, Url, Rev}) ->
    ok = filelib:ensure_dir(TmpDir),
    rebar_utils:sh(?FMT("bzr branch -r ~s ~s ~s",
                        [Rev, Url, filename:basename(TmpDir)]),
                   [{cd, filename:dirname(TmpDir)}]);
download_source_tmp(TmpDir, {svn, Url, Rev}) ->
    ok = filelib:ensure_dir(TmpDir),
    rebar_utils:sh(?FMT("svn checkout -r ~s ~s ~s",
                        [Rev, Url, filename:basename(TmpDir)]),
                   [{cd, filename:dirname(TmpDir)}]);
download_source_tmp(TmpDir, {rsync, Url}) ->
    ok = filelib:ensure_dir(TmpDir),
    rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, TmpDir]), []);
download_source_tmp(TmpDir, {fossil, Url}) ->
    download_source_tmp(TmpDir, {fossil, Url, ""});
download_source_tmp(TmpDir, {fossil, Url, Version}) ->
    Repository = filename:join(TmpDir, filename:basename(TmpDir) ++ ".fossil"),
    ok = filelib:ensure_dir(Repository),
    ok = file:set_cwd(TmpDir),
    rebar_utils:sh(?FMT("fossil clone ~s ~s", [Url, Repository]),
                   [{cd, TmpDir}]),
    rebar_utils:sh(?FMT("fossil open ~s ~s --nested", [Repository, Version]),
                   []);
download_source_tmp(TmpDir, {AppName, AppVersion, Url}) when is_binary(AppName)
                                                           , is_binary(AppVersion) ->
    TmpFile = binary_to_list(filename:join(TmpDir, <<AppName/binary, "-", AppVersion/binary>>)),
    {ok, saved_to_file} = httpc:request(get, {binary_to_list(Url), []}, [], [{stream, TmpFile}]),
    ok = erl_tar:extract(TmpFile, [{cwd, filename:dirname(TmpDir)}, compressed]),
    ok.

update_source1(AppDir, Args) when element(1, Args) =:= p4 ->
    download_source_tmp(AppDir, Args);
update_source1(AppDir, {git, Url}) ->
    update_source1(AppDir, {git, Url, {branch, "HEAD"}});
update_source1(AppDir, {git, Url, ""}) ->
    update_source1(AppDir, {git, Url, {branch, "HEAD"}});
update_source1(AppDir, {git, _Url, {branch, Branch}}) ->
    ShOpts = [{cd, AppDir}],
    rebar_utils:sh("git fetch origin", ShOpts),
    rebar_utils:sh(?FMT("git checkout -q ~s", [Branch]), ShOpts),
    rebar_utils:sh(
      ?FMT("git pull --ff-only --no-rebase -q origin ~s", [Branch]),ShOpts);
update_source1(AppDir, {git, _Url, {tag, Tag}}) ->
    ShOpts = [{cd, AppDir}],
    rebar_utils:sh("git fetch origin", ShOpts),
    rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), ShOpts);
update_source1(AppDir, {git, _Url, Refspec}) ->
    ShOpts = [{cd, AppDir}],
    rebar_utils:sh("git fetch origin", ShOpts),
    rebar_utils:sh(?FMT("git checkout -q ~s", [Refspec]), ShOpts);
update_source1(AppDir, {svn, _Url, Rev}) ->
    rebar_utils:sh(?FMT("svn up -r ~s", [Rev]), [{cd, AppDir}]);
update_source1(AppDir, {hg, _Url, Rev}) ->
    rebar_utils:sh(?FMT("hg pull -u -r ~s", [Rev]), [{cd, AppDir}]);
update_source1(AppDir, {bzr, _Url, Rev}) ->
    rebar_utils:sh(?FMT("bzr update -r ~s", [Rev]), [{cd, AppDir}]);
update_source1(AppDir, {rsync, Url}) ->
    rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]);
update_source1(AppDir, {fossil, Url}) ->
    update_source1(AppDir, {fossil, Url, ""});
update_source1(AppDir, {fossil, _Url, Version}) ->
    ok = file:set_cwd(AppDir),
    rebar_utils:sh("fossil pull", [{cd, AppDir}]),
    rebar_utils:sh(?FMT("fossil update ~s", [Version]), []).

%% ===================================================================
%% Source helper functions
%% ===================================================================

source_engine_avail(Source) ->
    Name = element(1, Source),
    source_engine_avail(Name, Source).

source_engine_avail(Name, Source)
  when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync;
       Name == fossil; Name == p4 ->
    case vcs_client_vsn(Name) >= required_vcs_client_vsn(Name) of
        true ->
            true;
        false ->
            ?ABORT("Rebar requires version ~p or higher of ~s to process ~p\n",
                   [required_vcs_client_vsn(Name), Name, Source])
    end.

vcs_client_vsn(false, _VsnArg, _VsnRegex) ->
    false;
vcs_client_vsn(Path, VsnArg, VsnRegex) ->
    {ok, Info} = rebar_utils:sh(Path ++ VsnArg, [{env, [{"LANG", "C"}]},
                                                 {use_stdout, false}]),
    case re:run(Info, VsnRegex, [{capture, all_but_first, list}]) of
        {match, Match} ->
            list_to_tuple([list_to_integer(S) || S <- Match]);
        _ ->
            false
    end.

required_vcs_client_vsn(p4)     -> {2013, 1};
required_vcs_client_vsn(hg)     -> {1, 1};
required_vcs_client_vsn(git)    -> {1, 5};
required_vcs_client_vsn(bzr)    -> {2, 0};
required_vcs_client_vsn(svn)    -> {1, 6};
required_vcs_client_vsn(rsync)  -> {2, 0};
required_vcs_client_vsn(fossil) -> {1, 0}.

vcs_client_vsn(p4) ->
    vcs_client_vsn(rebar_utils:find_executable("p4"), " -V",
                   "Rev\\. .*/(\\d+)\\.(\\d)/");
vcs_client_vsn(hg) ->
    vcs_client_vsn(rebar_utils:find_executable("hg"), " --version",
                   "version (\\d+).(\\d+)");
vcs_client_vsn(git) ->
    vcs_client_vsn(rebar_utils:find_executable("git"), " --version",
                   "git version (\\d+).(\\d+)");
vcs_client_vsn(bzr) ->
    vcs_client_vsn(rebar_utils:find_executable("bzr"), " --version",
                   "Bazaar \\(bzr\\) (\\d+).(\\d+)");
vcs_client_vsn(svn) ->
    vcs_client_vsn(rebar_utils:find_executable("svn"), " --version",
                   "svn, version (\\d+).(\\d+)");
vcs_client_vsn(rsync) ->
    vcs_client_vsn(rebar_utils:find_executable("rsync"), " --version",
                   "rsync  version (\\d+).(\\d+)");
vcs_client_vsn(fossil) ->
    vcs_client_vsn(rebar_utils:find_executable("fossil"), " version",
                   "version (\\d+).(\\d+)").

has_vcs_dir(p4, _) ->
    true;
has_vcs_dir(git, Dir) ->
    filelib:is_dir(filename:join(Dir, ".git"));
has_vcs_dir(hg, Dir) ->
    filelib:is_dir(filename:join(Dir, ".hg"));
has_vcs_dir(bzr, Dir) ->
    filelib:is_dir(filename:join(Dir, ".bzr"));
has_vcs_dir(svn, Dir) ->
    filelib:is_dir(filename:join(Dir, ".svn"))
        orelse filelib:is_dir(filename:join(Dir, "_svn"));
has_vcs_dir(rsync, _) ->
    true;
has_vcs_dir(_, _) ->
    true.

print_source({App, _, Source}) ->
    ?CONSOLE("~s~n", [format_source(App, Source)]).

format_source(App, {p4, Url}) ->
    format_source(App, {p4, Url, "#head"});
format_source(App, {git, Url}) ->
    ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]);
format_source(App, {git, Url, ""}) ->
    ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]);
format_source(App, {git, Url, {branch, Branch}}) ->
    ?FMT("~p BRANCH ~s ~s", [App, Branch, Url]);
format_source(App, {git, Url, {tag, Tag}}) ->
    ?FMT("~p TAG ~s ~s", [App, Tag, Url]);
format_source(App, {_, Url, Rev}) ->
    ?FMT("~p REV ~s ~s", [App, Rev, Url]);
format_source(App, undefined) ->
    ?FMT("~p", [App]).