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.

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