You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

404 lines
17 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  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_prv_install_deps).
  28. -behaviour(provider).
  29. -export([init/1,
  30. do/1,
  31. format_error/1]).
  32. -include("rebar.hrl").
  33. -include_lib("providers/include/providers.hrl").
  34. -export([do_/1,
  35. handle_deps_as_profile/4,
  36. profile_dep_dir/2,
  37. find_cycles/1,
  38. cull_compile/2]).
  39. -export_type([dep/0]).
  40. -define(PROVIDER, install_deps).
  41. -define(DEPS, [app_discovery]).
  42. -type src_dep() :: {atom(), {atom(), string(), string()}}
  43. | {atom(), string(), {atom(), string(), string()}}.
  44. -type pkg_dep() :: {atom(), binary()} | atom().
  45. -type dep() :: src_dep() | pkg_dep().
  46. %% ===================================================================
  47. %% Public API
  48. %% ===================================================================
  49. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  50. init(State) ->
  51. State1 = rebar_state:add_provider(State, providers:create([{name, ?PROVIDER},
  52. {module, ?MODULE},
  53. {bare, false},
  54. {deps, ?DEPS},
  55. {example, undefined},
  56. {short_desc, ""},
  57. {desc, ""},
  58. {opts, []}])),
  59. {ok, State1}.
  60. -spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
  61. do(State) ->
  62. ?INFO("Verifying dependencies...", []),
  63. do_(State).
  64. do_(State) ->
  65. try
  66. Profiles = rebar_state:current_profiles(State),
  67. ProjectApps = rebar_state:project_apps(State),
  68. Upgrade = rebar_state:get(State, upgrade, false),
  69. {Apps, State1} = deps_per_profile(Profiles, Upgrade, State),
  70. State2 = rebar_state:update_all_deps(State1, Apps),
  71. CodePaths = [rebar_app_info:ebin_dir(A) || A <- Apps],
  72. State3 = rebar_state:update_code_paths(State2, all_deps, CodePaths),
  73. Source = ProjectApps ++ Apps,
  74. case find_cycles(Source) of
  75. {cycles, Cycles} ->
  76. ?PRV_ERROR({cycles, Cycles});
  77. {error, Error} ->
  78. {error, Error};
  79. {no_cycle, Sorted} ->
  80. ToCompile = cull_compile(Sorted, ProjectApps),
  81. {ok, rebar_state:deps_to_build(State3, ToCompile)}
  82. end
  83. catch
  84. %% maybe_fetch will maybe_throw an exception to break out of some loops
  85. _:{error, Reason} ->
  86. {error, Reason}
  87. end.
  88. -spec format_error(any()) -> iolist().
  89. format_error({dep_app_not_found, AppDir, AppName}) ->
  90. io_lib:format("Dependency failure: Application ~s not found at the top level of directory ~s", [AppName, AppDir]);
  91. format_error({load_registry_fail, Dep}) ->
  92. io_lib:format("Error loading registry to resolve version of ~s. Try fixing by running 'rebar3 update'", [Dep]);
  93. format_error({bad_constraint, Name, Constraint}) ->
  94. io_lib:format("Unable to parse version for package ~s: ~s", [Name, Constraint]);
  95. format_error({parse_dep, Dep}) ->
  96. io_lib:format("Failed parsing dep ~p", [Dep]);
  97. format_error({not_rebar_package, Package, Version}) ->
  98. io_lib:format("Package not buildable with rebar3: ~s-~s", [Package, Version]);
  99. format_error({missing_package, Package, Version}) ->
  100. io_lib:format("Package not found in registry: ~s-~s", [Package, Version]);
  101. format_error({missing_package, Package}) ->
  102. io_lib:format("Package not found in registry: ~s", [Package]);
  103. format_error({cycles, Cycles}) ->
  104. Prints = [["applications: ",
  105. [io_lib:format("~s ", [Dep]) || Dep <- Cycle],
  106. "depend on each other~n"]
  107. || Cycle <- Cycles],
  108. ["Dependency cycle(s) detected:~n", Prints];
  109. format_error(Reason) ->
  110. io_lib:format("~p", [Reason]).
  111. %% Allows other providers to install deps in a given profile
  112. %% manually, outside of what is provided by rebar3's deps tuple.
  113. handle_deps_as_profile(Profile, State, Deps, Upgrade) ->
  114. Locks = [],
  115. Level = 0,
  116. DepsDir = profile_dep_dir(State, Profile),
  117. Deps1 = rebar_app_utils:parse_deps(DepsDir, Deps, State, Locks, Level),
  118. ProfileLevelDeps = [{Profile, Deps1, Level}],
  119. handle_profile_level(ProfileLevelDeps, [], sets:new(), Upgrade, Locks, State).
  120. %% ===================================================================
  121. %% Internal functions
  122. %% ===================================================================
  123. %% finds all the deps in `{deps, ...}` for each profile provided.
  124. deps_per_profile(Profiles, Upgrade, State) ->
  125. Level = 0,
  126. Locks = rebar_state:get(State, {locks, default}, []),
  127. Deps = lists:foldl(fun(Profile, DepAcc) ->
  128. [parsed_profile_deps(State, Profile, Level) | DepAcc]
  129. end, [], Profiles),
  130. handle_profile_level(Deps, [], sets:new(), Upgrade, Locks, State).
  131. parsed_profile_deps(State, Profile, Level) ->
  132. ParsedDeps = rebar_state:get(State, {parsed_deps, Profile}, []),
  133. {Profile, ParsedDeps, Level}.
  134. %% Level-order traversal of all dependencies, across profiles.
  135. %% If profiles x,y,z are present, then the traversal will go:
  136. %% x0, y0, z0, x1, y1, z1, ..., xN, yN, zN.
  137. handle_profile_level([], Apps, _Seen, _Upgrade, _Locks, State) ->
  138. {Apps, State};
  139. handle_profile_level([{Profile, Deps, Level} | Rest], Apps, Seen, Upgrade, Locks, State) ->
  140. {Deps1, Apps1, State1, Seen1} =
  141. update_deps(Profile, Level, Deps, Apps
  142. ,State, Upgrade, Seen, Locks),
  143. Deps2 = case Deps1 of
  144. [] -> Rest;
  145. _ -> Rest ++ [{Profile, Deps1, Level+1}]
  146. end,
  147. handle_profile_level(Deps2, Apps1, sets:union(Seen, Seen1), Upgrade, Locks, State1).
  148. find_cycles(Apps) ->
  149. case rebar_digraph:compile_order(Apps) of
  150. {error, {cycles, Cycles}} -> {cycles, Cycles};
  151. {error, Error} -> {error, Error};
  152. {ok, Sorted} -> {no_cycle, Sorted}
  153. end.
  154. cull_compile(TopSortedDeps, ProjectApps) ->
  155. lists:dropwhile(fun not_needs_compile/1, TopSortedDeps -- ProjectApps).
  156. maybe_lock(Profile, AppInfo, Seen, State, Level) ->
  157. Name = rebar_app_info:name(AppInfo),
  158. case rebar_app_info:is_checkout(AppInfo) of
  159. false ->
  160. case Profile of
  161. default ->
  162. case sets:is_element(Name, Seen) of
  163. false ->
  164. Locks = rebar_state:lock(State),
  165. case lists:any(fun(App) -> rebar_app_info:name(App) =:= Name end, Locks) of
  166. true ->
  167. {sets:add_element(Name, Seen), State};
  168. false ->
  169. {sets:add_element(Name, Seen),
  170. rebar_state:lock(State, rebar_app_info:dep_level(AppInfo, Level))}
  171. end;
  172. true ->
  173. {Seen, State}
  174. end;
  175. _ ->
  176. {sets:add_element(Name, Seen), State}
  177. end;
  178. true ->
  179. {sets:add_element(Name, Seen), State}
  180. end.
  181. update_deps(Profile, Level, Deps, Apps, State, Upgrade, Seen, Locks) ->
  182. lists:foldl(
  183. fun(AppInfo, {DepsAcc, AppsAcc, StateAcc, SeenAcc}) ->
  184. update_dep(AppInfo, Profile, Level,
  185. DepsAcc, AppsAcc, StateAcc,
  186. Upgrade, SeenAcc, Locks)
  187. end,
  188. {[], Apps, State, Seen},
  189. rebar_utils:sort_deps(Deps)).
  190. update_dep(AppInfo, Profile, Level, Deps, Apps, State, Upgrade, Seen, Locks) ->
  191. %% If not seen, add to list of locks to write out
  192. Name = rebar_app_info:name(AppInfo),
  193. case sets:is_element(Name, Seen) of
  194. true ->
  195. update_seen_dep(AppInfo, Profile, Level,
  196. Deps, Apps,
  197. State, Upgrade, Seen, Locks);
  198. false ->
  199. update_unseen_dep(AppInfo, Profile, Level,
  200. Deps, Apps,
  201. State, Upgrade, Seen, Locks)
  202. end.
  203. profile_dep_dir(State, Profile) ->
  204. case Profile of
  205. default -> filename:join([rebar_dir:profile_dir(rebar_state:opts(State), [default]), rebar_state:get(State, deps_dir, ?DEFAULT_DEPS_DIR)]);
  206. _ -> rebar_dir:deps_dir(State)
  207. end.
  208. update_seen_dep(AppInfo, _Profile, _Level, Deps, Apps, State, Upgrade, Seen, Locks) ->
  209. Name = rebar_app_info:name(AppInfo),
  210. %% If seen from lock file or user requested an upgrade
  211. %% don't print warning about skipping
  212. case lists:keymember(Name, 1, Locks) of
  213. false when Upgrade -> ok;
  214. false when not Upgrade -> warn_skip_deps(AppInfo, State);
  215. true -> ok
  216. end,
  217. {Deps, Apps, State, Seen}.
  218. update_unseen_dep(AppInfo, Profile, Level, Deps, Apps, State, Upgrade, Seen, Locks) ->
  219. {NewSeen, State1} = maybe_lock(Profile, AppInfo, Seen, State, Level),
  220. {_, AppInfo1} = maybe_fetch(AppInfo, Profile, Upgrade, Seen, State1),
  221. DepsDir = profile_dep_dir(State, Profile),
  222. {AppInfo2, NewDeps, State2} =
  223. handle_dep(State1, Profile, DepsDir, AppInfo1, Locks, Level),
  224. AppInfo3 = rebar_app_info:dep_level(AppInfo2, Level),
  225. {NewDeps ++ Deps, [AppInfo3 | Apps], State2, NewSeen}.
  226. -spec handle_dep(rebar_state:t(), atom(), file:filename_all(), rebar_app_info:t(), list(), integer()) -> {rebar_app_info:t(), [rebar_app_info:t()], rebar_state:t()}.
  227. handle_dep(State, Profile, DepsDir, AppInfo, Locks, Level) ->
  228. Name = rebar_app_info:name(AppInfo),
  229. C = rebar_config:consult(rebar_app_info:dir(AppInfo)),
  230. AppInfo0 = rebar_app_info:update_opts(AppInfo, rebar_app_info:opts(AppInfo), C),
  231. AppInfo1 = rebar_app_info:apply_overrides(rebar_app_info:get(AppInfo, overrides, []), AppInfo0),
  232. AppInfo2 = rebar_app_info:apply_profiles(AppInfo1, [default, prod]),
  233. Plugins = rebar_app_info:get(AppInfo2, plugins, []),
  234. AppInfo3 = rebar_app_info:set(AppInfo2, {plugins, Profile}, Plugins),
  235. %% Will throw an exception if checks fail
  236. rebar_app_info:verify_otp_vsn(AppInfo3),
  237. %% Dep may have plugins to install. Find and install here.
  238. State1 = rebar_plugins:install(State, AppInfo3),
  239. %% Upgrade lock level to be the level the dep will have in this dep tree
  240. Deps = rebar_app_info:get(AppInfo3, {deps, default}, []) ++ rebar_app_info:get(AppInfo3, {deps, prod}, []),
  241. AppInfo4 = rebar_app_info:deps(AppInfo3, rebar_state:deps_names(Deps)),
  242. %% Keep all overrides from the global config and this dep when parsing its deps
  243. Overrides = rebar_app_info:get(AppInfo0, overrides, []),
  244. Deps1 = rebar_app_utils:parse_deps(Name, DepsDir, Deps, rebar_state:set(State, overrides, Overrides)
  245. ,Locks, Level+1),
  246. {AppInfo4, Deps1, State1}.
  247. -spec maybe_fetch(rebar_app_info:t(), atom(), boolean(),
  248. sets:set(binary()), rebar_state:t()) -> {boolean(), rebar_app_info:t()}.
  249. maybe_fetch(AppInfo, Profile, Upgrade, Seen, State) ->
  250. AppDir = ec_cnv:to_list(rebar_app_info:dir(AppInfo)),
  251. %% Don't fetch dep if it exists in the _checkouts dir
  252. case rebar_app_info:is_checkout(AppInfo) of
  253. true ->
  254. {false, AppInfo};
  255. false ->
  256. case rebar_app_discover:find_app(AppInfo, AppDir, all) of
  257. false ->
  258. true = fetch_app(AppInfo, AppDir, State),
  259. maybe_symlink_default(State, Profile, AppDir, AppInfo),
  260. {true, rebar_app_info:valid(update_app_info(AppDir, AppInfo), false)};
  261. {true, AppInfo1} ->
  262. case sets:is_element(rebar_app_info:name(AppInfo1), Seen) of
  263. true ->
  264. {false, AppInfo1};
  265. false ->
  266. maybe_symlink_default(State, Profile, AppDir, AppInfo1),
  267. MaybeUpgrade = maybe_upgrade(AppInfo, AppDir, Upgrade, State),
  268. AppInfo2 = update_app_info(AppDir, AppInfo1),
  269. {MaybeUpgrade, AppInfo2}
  270. end
  271. end
  272. end.
  273. needs_symlinking(State, Profile) ->
  274. case {rebar_state:current_profiles(State), Profile} of
  275. {[default], default} ->
  276. %% file will be in default already -- this is the only run we have
  277. false;
  278. {_, default} ->
  279. %% file fetched to default, needs to be linked to the current
  280. %% run's directory.
  281. true;
  282. _ ->
  283. %% File fetched to the right directory already
  284. false
  285. end.
  286. maybe_symlink_default(State, Profile, AppDir, AppInfo) ->
  287. case needs_symlinking(State, Profile) of
  288. true ->
  289. SymDir = filename:join([rebar_dir:deps_dir(State),
  290. rebar_app_info:name(AppInfo)]),
  291. symlink_dep(State, AppDir, SymDir),
  292. true;
  293. false ->
  294. false
  295. end.
  296. symlink_dep(State, From, To) ->
  297. filelib:ensure_dir(To),
  298. case rebar_file_utils:symlink_or_copy(From, To) of
  299. ok ->
  300. RelativeFrom = make_relative_to_root(State, From),
  301. RelativeTo = make_relative_to_root(State, To),
  302. ?INFO("Linking ~s to ~s", [RelativeFrom, RelativeTo]),
  303. ok;
  304. exists ->
  305. ok
  306. end.
  307. make_relative_to_root(State, Path) when is_binary(Path) ->
  308. make_relative_to_root(State, binary_to_list(Path));
  309. make_relative_to_root(State, Path) when is_list(Path) ->
  310. Root = rebar_dir:root_dir(State),
  311. rebar_dir:make_relative_path(Path, Root).
  312. fetch_app(AppInfo, AppDir, State) ->
  313. ?INFO("Fetching ~s (~p)", [rebar_app_info:name(AppInfo), rebar_app_info:source(AppInfo)]),
  314. Source = rebar_app_info:source(AppInfo),
  315. true = rebar_fetch:download_source(AppDir, Source, State).
  316. %% This is called after the dep has been downloaded and unpacked, if it hadn't been already.
  317. %% So this is the first time for newly downloaded apps that its .app/.app.src data can
  318. %% be read in an parsed.
  319. update_app_info(AppDir, AppInfo) ->
  320. case rebar_app_discover:find_app(AppInfo, AppDir, all) of
  321. {true, AppInfo1} ->
  322. AppInfo1;
  323. false ->
  324. throw(?PRV_ERROR({dep_app_not_found, AppDir, rebar_app_info:name(AppInfo)}))
  325. end.
  326. maybe_upgrade(AppInfo, AppDir, Upgrade, State) ->
  327. Source = rebar_app_info:source(AppInfo),
  328. case Upgrade orelse rebar_app_info:is_lock(AppInfo) of
  329. true ->
  330. case rebar_fetch:needs_update(AppDir, Source, State) of
  331. true ->
  332. ?INFO("Upgrading ~s (~p)", [rebar_app_info:name(AppInfo), rebar_app_info:source(AppInfo)]),
  333. true = rebar_fetch:download_source(AppDir, Source, State);
  334. false ->
  335. case Upgrade of
  336. true ->
  337. ?INFO("No upgrade needed for ~s", [rebar_app_info:name(AppInfo)]),
  338. false;
  339. false ->
  340. false
  341. end
  342. end;
  343. false ->
  344. false
  345. end.
  346. warn_skip_deps(AppInfo, State) ->
  347. Msg = "Skipping ~s (from ~p) as an app of the same name "
  348. "has already been fetched",
  349. Args = [rebar_app_info:name(AppInfo),
  350. rebar_app_info:source(AppInfo)],
  351. case rebar_state:get(State, deps_error_on_conflict, false) of
  352. false -> ?WARN(Msg, Args);
  353. true -> ?ERROR(Msg, Args), ?FAIL
  354. end.
  355. not_needs_compile(App) ->
  356. not(rebar_app_info:is_checkout(App))
  357. andalso rebar_app_info:valid(App)
  358. andalso rebar_app_info:has_all_artifacts(App) =:= true.