25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

519 satır
20 KiB

14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
14 yıl önce
  1. %% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
  2. %% ex: ts=4 sw=4 et
  3. %% -------------------------------------------------------------------
  4. %%
  5. %% rebar: Erlang Build Tools
  6. %%
  7. %% Copyright (c) 2009 Dave Smith (dizzyd@dizzyd.com)
  8. %%
  9. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  10. %% of this software and associated documentation files (the "Software"), to deal
  11. %% in the Software without restriction, including without limitation the rights
  12. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. %% copies of the Software, and to permit persons to whom the Software is
  14. %% furnished to do so, subject to the following conditions:
  15. %%
  16. %% The above copyright notice and this permission notice shall be included in
  17. %% all copies or substantial portions of the Software.
  18. %%
  19. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. %% THE SOFTWARE.
  26. %% -------------------------------------------------------------------
  27. -module(rebar_deps).
  28. -include("rebar.hrl").
  29. -export([preprocess/2,
  30. postprocess/2,
  31. compile/2,
  32. setup_env/1,
  33. 'check-deps'/2,
  34. 'get-deps'/2,
  35. 'update-deps'/2,
  36. 'delete-deps'/2,
  37. 'list-deps'/2]).
  38. -record(dep, { dir,
  39. app,
  40. vsn_regex,
  41. source }).
  42. %% ===================================================================
  43. %% Public API
  44. %% ===================================================================
  45. preprocess(Config, _) ->
  46. %% Side effect to set deps_dir globally for all dependencies from
  47. %% top level down. Means the root deps_dir is honoured or the default
  48. %% used globally since it will be set on the first time through here
  49. set_global_deps_dir(Config, rebar_config:get_global(deps_dir, [])),
  50. %% Get the list of deps for the current working directory and identify those
  51. %% deps that are available/present.
  52. Deps = rebar_config:get_local(Config, deps, []),
  53. {AvailableDeps, MissingDeps} = find_deps(find, Deps),
  54. ?DEBUG("Available deps: ~p\n", [AvailableDeps]),
  55. ?DEBUG("Missing deps : ~p\n", [MissingDeps]),
  56. %% Add available deps to code path
  57. update_deps_code_path(AvailableDeps),
  58. %% If skip_deps=true, mark each dep dir as a skip_dir w/ the core so that
  59. %% the current command doesn't run on the dep dir. However, pre/postprocess
  60. %% WILL run (and we want it to) for transitivity purposes.
  61. case rebar_config:get_global(skip_deps, false) of
  62. "true" ->
  63. lists:foreach(fun (#dep{dir = Dir}) ->
  64. rebar_core:skip_dir(Dir)
  65. end, AvailableDeps);
  66. _ ->
  67. ok
  68. end,
  69. %% Return all the available dep directories for process
  70. {ok, [D#dep.dir || D <- AvailableDeps]}.
  71. postprocess(_Config, _) ->
  72. case erlang:get(?MODULE) of
  73. undefined ->
  74. {ok, []};
  75. Dirs ->
  76. erlang:erase(?MODULE),
  77. {ok, Dirs}
  78. end.
  79. compile(Config, AppFile) ->
  80. 'check-deps'(Config, AppFile).
  81. %% set REBAR_DEPS_DIR and ERL_LIBS environment variables
  82. setup_env(_Config) ->
  83. {true, DepsDir} = get_deps_dir(),
  84. %% include rebar's DepsDir in ERL_LIBS
  85. Separator = case os:type() of
  86. {win32, nt} ->
  87. ";";
  88. _ ->
  89. ":"
  90. end,
  91. ERL_LIBS = case os:getenv("ERL_LIBS") of
  92. false ->
  93. {"ERL_LIBS", DepsDir};
  94. PrevValue ->
  95. {"ERL_LIBS", DepsDir ++ Separator ++ PrevValue}
  96. end,
  97. [{"REBAR_DEPS_DIR", DepsDir}, ERL_LIBS].
  98. 'check-deps'(Config, _) ->
  99. %% Get the list of immediate (i.e. non-transitive) deps that are missing
  100. Deps = rebar_config:get_local(Config, deps, []),
  101. case find_deps(find, Deps) of
  102. {_, []} ->
  103. %% No missing deps
  104. ok;
  105. {_, MissingDeps} ->
  106. lists:foreach(fun (#dep{app=App, vsn_regex=Vsn, source=Src}) ->
  107. ?CONSOLE("Dependency not available: "
  108. "~p-~s (~p)\n", [App, Vsn, Src])
  109. end, MissingDeps),
  110. ?ABORT
  111. end.
  112. 'get-deps'(Config, _) ->
  113. %% Determine what deps are available and missing
  114. Deps = rebar_config:get_local(Config, deps, []),
  115. {_AvailableDeps, MissingDeps} = find_deps(find, Deps),
  116. %% For each missing dep with a specified source, try to pull it.
  117. PulledDeps = [use_source(D) || D <- MissingDeps, D#dep.source /= undefined],
  118. %% Add each pulled dep to our list of dirs for post-processing. This yields
  119. %% the necessary transitivity of the deps
  120. erlang:put(?MODULE, [D#dep.dir || D <- PulledDeps]),
  121. ok.
  122. 'update-deps'(Config, _) ->
  123. %% Determine what deps are available and missing
  124. Deps = rebar_config:get_local(Config, deps, []),
  125. UpdatedDeps = [update_source(D) || D <- find_deps(read, Deps),
  126. D#dep.source /= undefined],
  127. %% Add each updated dep to our list of dirs for post-processing. This yields
  128. %% the necessary transitivity of the deps
  129. erlang:put(?MODULE, [D#dep.dir || D <- UpdatedDeps]),
  130. ok.
  131. 'delete-deps'(Config, _) ->
  132. %% Delete all the available deps in our deps/ directory, if any
  133. {true, DepsDir} = get_deps_dir(),
  134. Deps = rebar_config:get_local(Config, deps, []),
  135. {AvailableDeps, _} = find_deps(find, Deps),
  136. _ = [delete_dep(D)
  137. || D <- AvailableDeps,
  138. lists:prefix(DepsDir, D#dep.dir)],
  139. ok.
  140. 'list-deps'(Config, _) ->
  141. Deps = rebar_config:get_local(Config, deps, []),
  142. case find_deps(find, Deps) of
  143. {AvailDeps, []} ->
  144. lists:foreach(fun(Dep) -> print_source(Dep) end, AvailDeps),
  145. ok;
  146. {_, MissingDeps} ->
  147. ?ABORT("Missing dependencies: ~p\n", [MissingDeps])
  148. end.
  149. %% ===================================================================
  150. %% Internal functions
  151. %% ===================================================================
  152. %% Added because of trans deps,
  153. %% need all deps in same dir and should be the one set by the root rebar.config
  154. %% Sets a default if root config has no deps_dir set
  155. set_global_deps_dir(Config, []) ->
  156. rebar_config:set_global(deps_dir,
  157. rebar_config:get_local(Config, deps_dir, "deps"));
  158. set_global_deps_dir(_Config, _DepsDir) ->
  159. ok.
  160. get_deps_dir() ->
  161. get_deps_dir("").
  162. get_deps_dir(App) ->
  163. BaseDir = rebar_config:get_global(base_dir, []),
  164. DepsDir = rebar_config:get_global(deps_dir, "deps"),
  165. {true, filename:join([BaseDir, DepsDir, App])}.
  166. get_lib_dir(App) ->
  167. %% Find App amongst the reachable lib directories
  168. %% Returns either the found path or a tagged tuple with a boolean
  169. %% to match get_deps_dir's return type
  170. case code:lib_dir(App) of
  171. {error, bad_name} -> {false, bad_name};
  172. Path -> {true, Path}
  173. end.
  174. update_deps_code_path([]) ->
  175. ok;
  176. update_deps_code_path([Dep | Rest]) ->
  177. case is_app_available(Dep#dep.app, Dep#dep.vsn_regex, Dep#dep.dir) of
  178. {true, _} ->
  179. Dir = filename:join(Dep#dep.dir, "ebin"),
  180. ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
  181. ?DEBUG("Adding ~s to code path~n", [Dir]),
  182. true = code:add_patha(Dir);
  183. {false, _} ->
  184. true
  185. end,
  186. update_deps_code_path(Rest).
  187. find_deps(find=Mode, Deps) ->
  188. find_deps(Mode, Deps, {[], []});
  189. find_deps(read=Mode, Deps) ->
  190. find_deps(Mode, Deps, []).
  191. find_deps(find, [], {Avail, Missing}) ->
  192. {lists:reverse(Avail), lists:reverse(Missing)};
  193. find_deps(read, [], Deps) ->
  194. lists:reverse(Deps);
  195. find_deps(Mode, [App | Rest], Acc) when is_atom(App) ->
  196. find_deps(Mode, [{App, ".*", undefined} | Rest], Acc);
  197. find_deps(Mode, [{App, VsnRegex} | Rest], Acc) when is_atom(App) ->
  198. find_deps(Mode, [{App, VsnRegex, undefined} | Rest], Acc);
  199. find_deps(Mode, [{App, VsnRegex, Source} | Rest], Acc) ->
  200. Dep = #dep { app = App,
  201. vsn_regex = VsnRegex,
  202. source = Source },
  203. {Availability, FoundDir} = find_dep(Dep),
  204. find_deps(Mode, Rest, acc_deps(Mode, Availability, Dep, FoundDir, Acc));
  205. find_deps(_Mode, [Other | _Rest], _Acc) ->
  206. ?ABORT("Invalid dependency specification ~p in ~s\n",
  207. [Other, rebar_utils:get_cwd()]).
  208. find_dep(Dep) ->
  209. %% Find a dep based on its source,
  210. %% e.g. {git, "https://github.com/mochi/mochiweb.git", "HEAD"}
  211. %% Deps with a source must be found (or fetched) locally.
  212. %% Those without a source may be satisfied from lib dir (get_lib_dir).
  213. find_dep(Dep, Dep#dep.source).
  214. find_dep(Dep, undefined) ->
  215. %% 'source' is undefined. If Dep is not satisfied locally,
  216. %% go ahead and find it amongst the lib_dir's.
  217. case find_dep_in_dir(Dep, get_deps_dir(Dep#dep.app)) of
  218. {avail, _Dir} = Avail -> Avail;
  219. {missing, _} -> find_dep_in_dir(Dep, get_lib_dir(Dep#dep.app))
  220. end;
  221. find_dep(Dep, _Source) ->
  222. %% _Source is defined. Regardless of what it is, we must find it
  223. %% locally satisfied or fetch it from the original source
  224. %% into the project's deps
  225. find_dep_in_dir(Dep, get_deps_dir(Dep#dep.app)).
  226. find_dep_in_dir(_Dep, {false, Dir}) ->
  227. {missing, Dir};
  228. find_dep_in_dir(Dep, {true, Dir}) ->
  229. App = Dep#dep.app,
  230. VsnRegex = Dep#dep.vsn_regex,
  231. case is_app_available(App, VsnRegex, Dir) of
  232. {true, _AppFile} -> {avail, Dir};
  233. {false, _} -> {missing, Dir}
  234. end.
  235. acc_deps(find, avail, Dep, AppDir, {Avail, Missing}) ->
  236. {[Dep#dep { dir = AppDir } | Avail], Missing};
  237. acc_deps(find, missing, Dep, AppDir, {Avail, Missing}) ->
  238. {Avail, [Dep#dep { dir = AppDir } | Missing]};
  239. acc_deps(read, _, Dep, AppDir, Acc) ->
  240. [Dep#dep { dir = AppDir } | Acc].
  241. delete_dep(D) ->
  242. case filelib:is_dir(D#dep.dir) of
  243. true ->
  244. ?INFO("Deleting dependency: ~s\n", [D#dep.dir]),
  245. rebar_file_utils:rm_rf(D#dep.dir);
  246. false ->
  247. ok
  248. end.
  249. require_source_engine(Source) ->
  250. true = source_engine_avail(Source),
  251. ok.
  252. is_app_available(App, VsnRegex, Path) ->
  253. ?DEBUG("is_app_available, looking for App ~p with Path ~p~n", [App, Path]),
  254. case rebar_app_utils:is_app_dir(Path) of
  255. {true, AppFile} ->
  256. case rebar_app_utils:app_name(AppFile) of
  257. App ->
  258. Vsn = rebar_app_utils:app_vsn(AppFile),
  259. ?INFO("Looking for ~s-~s ; found ~s-~s at ~s\n",
  260. [App, VsnRegex, App, Vsn, Path]),
  261. case re:run(Vsn, VsnRegex, [{capture, none}]) of
  262. match ->
  263. {true, Path};
  264. nomatch ->
  265. ?WARN("~s has version ~p; requested regex was ~s\n",
  266. [AppFile, Vsn, VsnRegex]),
  267. {false, {version_mismatch,
  268. {AppFile,
  269. {expected, VsnRegex}, {has, Vsn}}}}
  270. end;
  271. OtherApp ->
  272. ?WARN("~s has application id ~p; expected ~p\n",
  273. [AppFile, OtherApp, App]),
  274. {false, {name_mismatch,
  275. {AppFile, {expected, App}, {has, OtherApp}}}}
  276. end;
  277. false ->
  278. ?WARN("Expected ~s to be an app dir (containing ebin/*.app), "
  279. "but no .app found.\n", [Path]),
  280. {false, {missing_app_file, Path}}
  281. end.
  282. use_source(Dep) ->
  283. use_source(Dep, 3).
  284. use_source(Dep, 0) ->
  285. ?ABORT("Failed to acquire source from ~p after 3 tries.\n",
  286. [Dep#dep.source]);
  287. use_source(Dep, Count) ->
  288. case filelib:is_dir(Dep#dep.dir) of
  289. true ->
  290. %% Already downloaded -- verify the versioning matches the regex
  291. case is_app_available(Dep#dep.app,
  292. Dep#dep.vsn_regex, Dep#dep.dir) of
  293. {true, _} ->
  294. Dir = filename:join(Dep#dep.dir, "ebin"),
  295. ok = filelib:ensure_dir(filename:join(Dir, "dummy")),
  296. %% Available version matches up -- we're good to go;
  297. %% add the app dir to our code path
  298. true = code:add_patha(Dir),
  299. Dep;
  300. {false, Reason} ->
  301. %% The app that was downloaded doesn't match up (or had
  302. %% errors or something). For the time being, abort.
  303. ?ABORT("Dependency dir ~s failed application validation "
  304. "with reason:~n~p.\n", [Dep#dep.dir, Reason])
  305. end;
  306. false ->
  307. ?CONSOLE("Pulling ~p from ~p\n", [Dep#dep.app, Dep#dep.source]),
  308. require_source_engine(Dep#dep.source),
  309. {true, TargetDir} = get_deps_dir(Dep#dep.app),
  310. download_source(TargetDir, Dep#dep.source),
  311. use_source(Dep#dep { dir = TargetDir }, Count-1)
  312. end.
  313. download_source(AppDir, {hg, Url, Rev}) ->
  314. ok = filelib:ensure_dir(AppDir),
  315. rebar_utils:sh(?FMT("hg clone -U ~s ~s", [Url, filename:basename(AppDir)]),
  316. [{cd, filename:dirname(AppDir)}]),
  317. rebar_utils:sh(?FMT("hg update ~s", [Rev]), [{cd, AppDir}]);
  318. download_source(AppDir, {git, Url}) ->
  319. download_source(AppDir, {git, Url, {branch, "HEAD"}});
  320. download_source(AppDir, {git, Url, ""}) ->
  321. download_source(AppDir, {git, Url, {branch, "HEAD"}});
  322. download_source(AppDir, {git, Url, {branch, Branch}}) ->
  323. ok = filelib:ensure_dir(AppDir),
  324. rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]),
  325. [{cd, filename:dirname(AppDir)}]),
  326. rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), [{cd, AppDir}]);
  327. download_source(AppDir, {git, Url, {tag, Tag}}) ->
  328. ok = filelib:ensure_dir(AppDir),
  329. rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]),
  330. [{cd, filename:dirname(AppDir)}]),
  331. rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), [{cd, AppDir}]);
  332. download_source(AppDir, {git, Url, Rev}) ->
  333. ok = filelib:ensure_dir(AppDir),
  334. rebar_utils:sh(?FMT("git clone -n ~s ~s", [Url, filename:basename(AppDir)]),
  335. [{cd, filename:dirname(AppDir)}]),
  336. rebar_utils:sh(?FMT("git checkout -q ~s", [Rev]), [{cd, AppDir}]);
  337. download_source(AppDir, {bzr, Url, Rev}) ->
  338. ok = filelib:ensure_dir(AppDir),
  339. rebar_utils:sh(?FMT("bzr branch -r ~s ~s ~s",
  340. [Rev, Url, filename:basename(AppDir)]),
  341. [{cd, filename:dirname(AppDir)}]);
  342. download_source(AppDir, {svn, Url, Rev}) ->
  343. ok = filelib:ensure_dir(AppDir),
  344. rebar_utils:sh(?FMT("svn checkout -r ~s ~s ~s",
  345. [Rev, Url, filename:basename(AppDir)]),
  346. [{cd, filename:dirname(AppDir)}]);
  347. download_source(AppDir, {rsync, Url}) ->
  348. ok = filelib:ensure_dir(AppDir),
  349. rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s", [Url, AppDir]), []).
  350. update_source(Dep) ->
  351. %% It's possible when updating a source, that a given dep does not have a
  352. %% VCS directory, such as when a source archive is built of a project, with
  353. %% all deps already downloaded/included. So, verify that the necessary VCS
  354. %% directory exists before attempting to do the update.
  355. {true, AppDir} = get_deps_dir(Dep#dep.app),
  356. case has_vcs_dir(element(1, Dep#dep.source), AppDir) of
  357. true ->
  358. ?CONSOLE("Updating ~p from ~p\n", [Dep#dep.app, Dep#dep.source]),
  359. require_source_engine(Dep#dep.source),
  360. update_source(AppDir, Dep#dep.source),
  361. Dep;
  362. false ->
  363. ?WARN("Skipping update for ~p: "
  364. "no VCS directory available!\n", [Dep]),
  365. Dep
  366. end.
  367. update_source(AppDir, {git, Url}) ->
  368. update_source(AppDir, {git, Url, {branch, "HEAD"}});
  369. update_source(AppDir, {git, Url, ""}) ->
  370. update_source(AppDir, {git, Url, {branch, "HEAD"}});
  371. update_source(AppDir, {git, _Url, {branch, Branch}}) ->
  372. ShOpts = [{cd, AppDir}],
  373. rebar_utils:sh("git fetch origin", ShOpts),
  374. rebar_utils:sh(?FMT("git checkout -q origin/~s", [Branch]), ShOpts);
  375. update_source(AppDir, {git, _Url, {tag, Tag}}) ->
  376. ShOpts = [{cd, AppDir}],
  377. rebar_utils:sh("git fetch --tags origin", ShOpts),
  378. rebar_utils:sh(?FMT("git checkout -q ~s", [Tag]), ShOpts);
  379. update_source(AppDir, {git, _Url, Refspec}) ->
  380. ShOpts = [{cd, AppDir}],
  381. rebar_utils:sh("git fetch origin", ShOpts),
  382. rebar_utils:sh(?FMT("git checkout -q ~s", [Refspec]), ShOpts);
  383. update_source(AppDir, {svn, _Url, Rev}) ->
  384. rebar_utils:sh(?FMT("svn up -r ~s", [Rev]), [{cd, AppDir}]);
  385. update_source(AppDir, {hg, _Url, Rev}) ->
  386. rebar_utils:sh(?FMT("hg pull -u -r ~s", [Rev]), [{cd, AppDir}]);
  387. update_source(AppDir, {bzr, _Url, Rev}) ->
  388. rebar_utils:sh(?FMT("bzr update -r ~s", [Rev]), [{cd, AppDir}]);
  389. update_source(AppDir, {rsync, Url}) ->
  390. rebar_utils:sh(?FMT("rsync -az --delete ~s/ ~s",[Url,AppDir]),[]).
  391. %% ===================================================================
  392. %% Source helper functions
  393. %% ===================================================================
  394. source_engine_avail(Source) ->
  395. Name = element(1, Source),
  396. source_engine_avail(Name, Source).
  397. source_engine_avail(Name, Source)
  398. when Name == hg; Name == git; Name == svn; Name == bzr; Name == rsync ->
  399. case vcs_client_vsn(Name) >= required_vcs_client_vsn(Name) of
  400. true ->
  401. true;
  402. false ->
  403. ?ABORT("Rebar requires version ~p or higher of ~s to process ~p\n",
  404. [required_vcs_client_vsn(Name), Name, Source])
  405. end.
  406. vcs_client_vsn(false, _VsnArg, _VsnRegex) ->
  407. false;
  408. vcs_client_vsn(Path, VsnArg, VsnRegex) ->
  409. {ok, Info} = rebar_utils:sh(Path ++ VsnArg, [{env, [{"LANG", "C"}]},
  410. {use_stdout, false}]),
  411. case re:run(Info, VsnRegex, [{capture, all_but_first, list}]) of
  412. {match, Match} ->
  413. list_to_tuple([list_to_integer(S) || S <- Match]);
  414. _ ->
  415. false
  416. end.
  417. required_vcs_client_vsn(hg) -> {1, 1};
  418. required_vcs_client_vsn(git) -> {1, 5};
  419. required_vcs_client_vsn(bzr) -> {2, 0};
  420. required_vcs_client_vsn(svn) -> {1, 6};
  421. required_vcs_client_vsn(rsync) -> {2, 0}.
  422. vcs_client_vsn(hg) ->
  423. vcs_client_vsn(rebar_utils:find_executable("hg"), " --version",
  424. "version (\\d+).(\\d+)");
  425. vcs_client_vsn(git) ->
  426. vcs_client_vsn(rebar_utils:find_executable("git"), " --version",
  427. "git version (\\d+).(\\d+)");
  428. vcs_client_vsn(bzr) ->
  429. vcs_client_vsn(rebar_utils:find_executable("bzr"), " --version",
  430. "Bazaar \\(bzr\\) (\\d+).(\\d+)");
  431. vcs_client_vsn(svn) ->
  432. vcs_client_vsn(rebar_utils:find_executable("svn"), " --version",
  433. "svn, version (\\d+).(\\d+)");
  434. vcs_client_vsn(rsync) ->
  435. vcs_client_vsn(rebar_utils:find_executable("rsync"), " --version",
  436. "rsync version (\\d+).(\\d+)").
  437. has_vcs_dir(git, Dir) ->
  438. filelib:is_dir(filename:join(Dir, ".git"));
  439. has_vcs_dir(hg, Dir) ->
  440. filelib:is_dir(filename:join(Dir, ".hg"));
  441. has_vcs_dir(bzr, Dir) ->
  442. filelib:is_dir(filename:join(Dir, ".bzr"));
  443. has_vcs_dir(svn, Dir) ->
  444. filelib:is_dir(filename:join(Dir, ".svn"))
  445. orelse filelib:is_dir(filename:join(Dir, "_svn"));
  446. has_vcs_dir(rsync, _) ->
  447. true;
  448. has_vcs_dir(_, _) ->
  449. true.
  450. print_source(#dep{app=App, source=Source}) ->
  451. ?CONSOLE("~s~n", [format_source(App, Source)]).
  452. format_source(App, {git, Url}) ->
  453. ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]);
  454. format_source(App, {git, Url, ""}) ->
  455. ?FMT("~p BRANCH ~s ~s", [App, "HEAD", Url]);
  456. format_source(App, {git, Url, {branch, Branch}}) ->
  457. ?FMT("~p BRANCH ~s ~s", [App, Branch, Url]);
  458. format_source(App, {git, Url, {tag, Tag}}) ->
  459. ?FMT("~p TAG ~s ~s", [App, Tag, Url]);
  460. format_source(App, {_, Url, Rev}) ->
  461. ?FMT("~p REV ~s ~s", [App, Rev, Url]);
  462. format_source(App, undefined) ->
  463. ?FMT("~p", [App]).