您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

239 行
9.0 KiB

  1. %% -*- tab-width: 4;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. distclean/2]).
  31. -define(HG_VERSION, "1.4").
  32. %% ===================================================================
  33. %% Public API
  34. %% ===================================================================
  35. preprocess(Config, _) ->
  36. %% Get the directory where we will place downloaded deps. Take steps
  37. %% to ensure that if we're doing a multi-level build, all the deps will
  38. %% wind up in a single directory; avoiding potential pain from having
  39. %% multiple copies of the same dep scattered throughout the source tree.
  40. %%
  41. %% The first definition of deps_dir is the one we use; we also fully
  42. %% qualify it to ensure everyone sees it properly.
  43. case rebar_config:get_all(Config, deps_dir) of
  44. [] ->
  45. DepsDir = filename:absname("deps");
  46. AllDirs ->
  47. DepsDir = filename:absname(hd(lists:reverse(AllDirs)))
  48. end,
  49. ?DEBUG("~s: Using deps dir: ~s\n", [rebar_utils:get_cwd(), DepsDir]),
  50. Config2 = rebar_config:set(Config, deps_dir, DepsDir),
  51. %% Process the list of local deps from the configuration
  52. case catch(process_deps(rebar_config:get_local(Config, deps, []), [], DepsDir)) of
  53. Dirs when is_list(Dirs) ->
  54. {ok, Config2, Dirs};
  55. {'EXIT', Reason} ->
  56. ?ABORT("Error while processing dependencies: ~p\n", [Reason])
  57. end.
  58. distclean(Config, _) ->
  59. %% Delete all the deps which we downloaded (or would have caused to be
  60. %% downloaded).
  61. DepsDir = rebar_config:get(Config, deps_dir, rebar_utils:get_cwd()),
  62. ?DEBUG("Delete deps: ~p\n", [rebar_config:get(Config, deps, [])]),
  63. delete_deps(rebar_config:get_local(Config, deps, []), DepsDir).
  64. %% ===================================================================
  65. %% Internal functions
  66. %% ===================================================================
  67. process_deps([], Acc, _Dir) ->
  68. Acc;
  69. process_deps([App | Rest], Acc, Dir) when is_atom(App) ->
  70. require_app(App, ".*"),
  71. process_deps(Rest, Acc, Dir);
  72. process_deps([{App, VsnRegex} | Rest], Acc, Dir) when is_atom(App) ->
  73. require_app(App, VsnRegex),
  74. process_deps(Rest, Acc, Dir);
  75. process_deps([{App, VsnRegex, Source} | Rest], Acc, Dir) ->
  76. case is_app_available(App, VsnRegex) of
  77. true ->
  78. process_deps(Rest, Acc, Dir);
  79. false ->
  80. %% App may not be on the code path, or the version that is doesn't
  81. %% match our regex. Either way, we want to pull our revision into
  82. %% the deps dir and try to use that
  83. require_source_engine(Source),
  84. AppDir = filename:join(Dir, App),
  85. use_source(AppDir, App, VsnRegex, Source),
  86. process_deps(Rest, [AppDir | Acc], Dir)
  87. end;
  88. process_deps([Other | _Rest], _Acc, _Dir) ->
  89. ?ABORT("Invalid dependency specification ~p in ~s\n",
  90. [Other, rebar_utils:get_cwd()]).
  91. delete_deps([], _DepsDir) ->
  92. ok;
  93. delete_deps([{App, _VsnRegex, _Source} | Rest], DepsDir) ->
  94. AppDir = filename:join(DepsDir, App),
  95. case filelib:is_dir(AppDir) of
  96. true ->
  97. ?INFO("Delete dependency dir ~s\n", [AppDir]),
  98. rebar_file_utils:rm_rf(AppDir);
  99. false ->
  100. ok
  101. end,
  102. delete_deps(Rest, DepsDir);
  103. delete_deps([_Other | Rest], DepsDir) ->
  104. delete_deps(Rest, DepsDir).
  105. require_app(App, VsnRegex) ->
  106. case is_app_available(App, VsnRegex) of
  107. true ->
  108. ok;
  109. false ->
  110. %% The requested app is not available on the code path
  111. ?ABORT("~s: Dependency ~s-~s not available.\n",
  112. [rebar_utils:get_cwd(), App, VsnRegex])
  113. end.
  114. require_source_engine(Source) ->
  115. case source_engine_avail(Source) of
  116. true ->
  117. ok;
  118. false ->
  119. ?ABORT("No command line interface available to process ~p\n", [Source])
  120. end.
  121. is_app_available(App, VsnRegex) ->
  122. case code:lib_dir(App) of
  123. {error, bad_name} ->
  124. false;
  125. Path ->
  126. is_app_available(App, VsnRegex, Path)
  127. end.
  128. is_app_available(App, VsnRegex, Path) ->
  129. case rebar_app_utils:is_app_dir(Path) of
  130. {true, AppFile} ->
  131. case rebar_app_utils:load_app_file(AppFile) of
  132. {ok, App, AppData} ->
  133. {vsn, Vsn} = lists:keyfind(vsn, 1, AppData),
  134. ?INFO("Looking for ~s-~s ; found ~s-~s at ~s\n",
  135. [App, VsnRegex, App, Vsn, Path]),
  136. case re:run(Vsn, VsnRegex, [{capture, none}]) of
  137. match ->
  138. true;
  139. nomatch ->
  140. ?WARN("~s has version ~p; requested regex was ~s\n",
  141. [AppFile, Vsn, VsnRegex]),
  142. false
  143. end;
  144. {ok, OtherApp, _} ->
  145. ?WARN("~s has application id ~p; expected ~p\n", [AppFile, OtherApp, App]),
  146. false;
  147. {error, Reason} ->
  148. ?ABORT("Failed to parse ~s: ~p\n", [AppFile, Reason])
  149. end;
  150. false ->
  151. ?WARN("Expected ~s to be an app dir (containing ebin/*.app), but no .app found.\n",
  152. [Path]),
  153. false
  154. end.
  155. use_source(AppDir, App, VsnRegex, Source) ->
  156. use_source(AppDir, App, VsnRegex, Source, 3).
  157. use_source(_AppDir, _App, _VsnRegex, Source, 0) ->
  158. ?ABORT("Failed to acquire source from ~p after 3 tries.\n", [Source]);
  159. use_source(AppDir, App, VsnRegex, Source, Count) ->
  160. case filelib:is_dir(AppDir) of
  161. true ->
  162. %% Already downloaded -- verify the versioning matches up with our regex
  163. case is_app_available(App, VsnRegex, AppDir) of
  164. true ->
  165. %% Available version matches up -- we're good to go; add the
  166. %% app dir to our code path
  167. code:add_patha(filename:join(AppDir, ebin)),
  168. ok;
  169. false ->
  170. %% The app that was downloaded doesn't match up (or had
  171. %% errors or something). For the time being, abort.
  172. ?ABORT("Dependency dir ~s does not satisfy version regex ~s.\n",
  173. [AppDir, VsnRegex])
  174. end;
  175. false ->
  176. download_source(AppDir, Source),
  177. use_source(AppDir, App, VsnRegex, Source, Count-1)
  178. end.
  179. download_source(AppDir, {hg, Url, Rev}) ->
  180. ok = filelib:ensure_dir(AppDir),
  181. Cmd = ?FMT("hg clone -u ~s ~s", [Rev, Url]),
  182. rebar_utils:sh(Cmd, [], filename:dirname(AppDir)).
  183. %% ===================================================================
  184. %% Source helper functions
  185. %% ===================================================================
  186. source_engine_avail({hg, _, _}) ->
  187. Path = os:find_executable("hg"),
  188. ?DEBUG("which hg = ~p\n", [Path]),
  189. case Path of
  190. false ->
  191. false;
  192. _ ->
  193. ensure_required_scm_client(hg, Path)
  194. end;
  195. source_engine_avail(_) ->
  196. false.
  197. %% We expect the initial part of the version string to be of the form:
  198. %% "Mercurial Distributed SCM (version 1.4.1+20091201)". Rebar
  199. %% requires version 1.4 or higher.
  200. ensure_required_scm_client(hg, Path) ->
  201. Info = os:cmd(Path ++ " --version"),
  202. case re:run(Info, "version (\\d*\.\\d*\.\\d*)", [{capture, all_but_first, list}]) of
  203. {match, [Vsn]} ->
  204. case Vsn >= ?HG_VERSION of
  205. true ->
  206. true;
  207. false ->
  208. ?ABORT("Rebar requires version ~p or higher of hg (~p)~n",
  209. [?HG_VERSION, Path])
  210. end;
  211. _ ->
  212. false
  213. end.