Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

519 lignes
20 KiB

il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
il y a 14 ans
  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]).