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.

369 line
14 KiB

14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
  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_reltool).
  28. -export([generate/2,
  29. overlay/2,
  30. clean/2]).
  31. -include("rebar.hrl").
  32. -include_lib("kernel/include/file.hrl").
  33. %% ===================================================================
  34. %% Public API
  35. %% ===================================================================
  36. generate(Config0, ReltoolFile) ->
  37. %% Make sure we have decent version of reltool available
  38. check_vsn(),
  39. %% Load the reltool configuration from the file
  40. {Config, ReltoolConfig} = rebar_rel_utils:load_config(Config0, ReltoolFile),
  41. Sys = rebar_rel_utils:get_sys_tuple(ReltoolConfig),
  42. %% Spin up reltool server and load our config into it
  43. {ok, Server} = reltool:start_server([Sys]),
  44. %% Do some validation of the reltool configuration; error messages out of
  45. %% reltool are still pretty cryptic
  46. validate_rel_apps(Server, Sys),
  47. %% Finally, run reltool
  48. case catch(run_reltool(Server, Config, ReltoolConfig)) of
  49. ok ->
  50. {ok, Config};
  51. {error, failed} ->
  52. ?FAIL;
  53. Other2 ->
  54. ?ERROR("Unexpected error: ~p\n", [Other2]),
  55. ?FAIL
  56. end.
  57. overlay(Config, ReltoolFile) ->
  58. %% Load the reltool configuration from the file
  59. {Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile),
  60. {Config1, process_overlay(Config, ReltoolConfig)}.
  61. clean(Config, ReltoolFile) ->
  62. {Config1, ReltoolConfig} = rebar_rel_utils:load_config(Config, ReltoolFile),
  63. TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
  64. rebar_file_utils:rm_rf(TargetDir),
  65. rebar_file_utils:delete_each(["reltool.spec"]),
  66. {ok, Config1}.
  67. %% ===================================================================
  68. %% Internal functions
  69. %% ===================================================================
  70. check_vsn() ->
  71. %% TODO: use application:load and application:get_key once we require
  72. %% R14A or newer. There's no reltool.app before R14A.
  73. case code:lib_dir(reltool) of
  74. {error, bad_name} ->
  75. ?ABORT("Reltool support requires the reltool application "
  76. "to be installed!", []);
  77. Path ->
  78. ReltoolVsn = filename:basename(Path),
  79. case ReltoolVsn < "reltool-0.5.2" of
  80. true ->
  81. ?ABORT("Reltool support requires at least reltool-0.5.2; "
  82. "this VM is using ~s\n", [ReltoolVsn]);
  83. false ->
  84. ok
  85. end
  86. end.
  87. process_overlay(Config, ReltoolConfig) ->
  88. TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
  89. {_BootRelName, BootRelVsn} =
  90. rebar_rel_utils:get_reltool_release_info(ReltoolConfig),
  91. %% Initialize overlay vars with some basics
  92. %% (that can get overwritten)
  93. OverlayVars0 =
  94. dict:from_list([{erts_vsn, "erts-" ++ erlang:system_info(version)},
  95. {rel_vsn, BootRelVsn},
  96. {target_dir, TargetDir},
  97. {hostname, net_adm:localhost()}]),
  98. %% Load up any variables specified by overlay_vars
  99. OverlayVars1 = overlay_vars(Config, OverlayVars0, ReltoolConfig),
  100. OverlayVars = rebar_templater:resolve_variables(dict:to_list(OverlayVars1),
  101. OverlayVars1),
  102. %% Finally, overlay the files specified by the overlay section
  103. case lists:keyfind(overlay, 1, ReltoolConfig) of
  104. {overlay, Overlay} when is_list(Overlay) ->
  105. execute_overlay(Overlay, OverlayVars, rebar_utils:get_cwd(),
  106. TargetDir);
  107. false ->
  108. ?INFO("No {overlay, [...]} found in reltool.config.\n", []);
  109. _ ->
  110. ?ABORT("{overlay, [...]} entry in reltool.config "
  111. "must be a list.\n", [])
  112. end.
  113. %%
  114. %% Look for overlay_vars file reference. If the user provides an overlay_vars on
  115. %% the command line (i.e. a global), the terms from that file OVERRIDE the one
  116. %% listed in reltool.config. To re-iterate, this means you can specify a
  117. %% variable in the file from reltool.config and then override that value by
  118. %% providing an additional file on the command-line.
  119. %%
  120. overlay_vars(Config, Vars0, ReltoolConfig) ->
  121. BaseVars = load_vars_file(proplists:get_value(overlay_vars, ReltoolConfig)),
  122. OverrideVars = load_vars_file(rebar_config:get_global(Config,
  123. overlay_vars,
  124. undefined)),
  125. M = fun(_Key, _Base, Override) -> Override end,
  126. dict:merge(M, dict:merge(M, Vars0, BaseVars), OverrideVars).
  127. %%
  128. %% If a filename is provided, construct a dict of terms
  129. %%
  130. load_vars_file(undefined) ->
  131. dict:new();
  132. load_vars_file(File) ->
  133. case rebar_config:consult_file(File) of
  134. {ok, Terms} ->
  135. dict:from_list(Terms);
  136. {error, Reason} ->
  137. ?ABORT("Unable to load overlay_vars from ~p: ~p\n", [File, Reason])
  138. end.
  139. validate_rel_apps(ReltoolServer, {sys, ReltoolConfig}) ->
  140. case lists:keyfind(rel, 1, ReltoolConfig) of
  141. false ->
  142. ok;
  143. {rel, _Name, _Vsn, Apps} ->
  144. %% Identify all the apps that do NOT exist, based on
  145. %% what's available from the reltool server
  146. Missing = lists:sort(
  147. [App || App <- Apps,
  148. app_exists(App, ReltoolServer) == false]),
  149. case Missing of
  150. [] ->
  151. ok;
  152. _ ->
  153. ?ABORT("Apps in {rel, ...} section not found by "
  154. "reltool: ~p\n", [Missing])
  155. end;
  156. Rel ->
  157. %% Invalid release format!
  158. ?ABORT("Invalid {rel, ...} section in reltools.config: ~p\n", [Rel])
  159. end.
  160. app_exists(App, Server) when is_atom(App) ->
  161. case reltool_server:get_app(Server, App) of
  162. {ok, _} ->
  163. true;
  164. _ ->
  165. false
  166. end;
  167. app_exists(AppTuple, Server) when is_tuple(AppTuple) ->
  168. app_exists(element(1, AppTuple), Server).
  169. run_reltool(Server, Config, ReltoolConfig) ->
  170. case reltool:get_target_spec(Server) of
  171. {ok, Spec} ->
  172. %% Pull the target dir and make sure it exists
  173. TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig),
  174. mk_target_dir(Config, TargetDir),
  175. %% Determine the otp root dir to use
  176. RootDir = rebar_rel_utils:get_root_dir(Config, ReltoolConfig),
  177. %% Dump the spec, if necessary
  178. dump_spec(Config, Spec),
  179. %% Have reltool actually run
  180. case reltool:eval_target_spec(Spec, RootDir, TargetDir) of
  181. ok ->
  182. ok;
  183. {error, Reason} ->
  184. ?ABORT("Failed to generate target from spec: ~p\n",
  185. [Reason])
  186. end,
  187. {BootRelName, BootRelVsn} =
  188. rebar_rel_utils:get_reltool_release_info(ReltoolConfig),
  189. ok = create_RELEASES(TargetDir, BootRelName, BootRelVsn),
  190. process_overlay(Config, ReltoolConfig);
  191. {error, Reason} ->
  192. ?ABORT("Unable to generate spec: ~s\n", [Reason])
  193. end.
  194. mk_target_dir(Config, TargetDir) ->
  195. case filelib:ensure_dir(filename:join(TargetDir, "dummy")) of
  196. ok ->
  197. ok;
  198. {error, eexist} ->
  199. %% Output directory already exists; if force=1, wipe it out
  200. case rebar_config:get_global(Config, force, "0") of
  201. "1" ->
  202. rebar_file_utils:rm_rf(TargetDir),
  203. ok = file:make_dir(TargetDir);
  204. _ ->
  205. ?ERROR("Release target directory ~p already exists!\n",
  206. [TargetDir]),
  207. ?FAIL
  208. end;
  209. {error, Reason} ->
  210. ?ERROR("Failed to make target dir ~p: ~s\n",
  211. [TargetDir, file:format_error(Reason)]),
  212. ?FAIL
  213. end.
  214. dump_spec(Config, Spec) ->
  215. case rebar_config:get_global(Config, dump_spec, "0") of
  216. "1" ->
  217. SpecBin = list_to_binary(io_lib:print(Spec, 1, 120, -1)),
  218. ok = file:write_file("reltool.spec", SpecBin);
  219. _ ->
  220. ok
  221. end.
  222. %% TODO: Merge functionality here with rebar_templater
  223. execute_overlay([], _Vars, _BaseDir, _TargetDir) ->
  224. ok;
  225. execute_overlay([{mkdir, Out} | Rest], Vars, BaseDir, TargetDir) ->
  226. OutFile = rebar_templater:render(
  227. filename:join([TargetDir, Out, "dummy"]), Vars),
  228. ok = filelib:ensure_dir(OutFile),
  229. ?DEBUG("Created dir ~s\n", [filename:dirname(OutFile)]),
  230. execute_overlay(Rest, Vars, BaseDir, TargetDir);
  231. execute_overlay([{copy, In} | Rest], _Vars, BaseDir, TargetDir) ->
  232. execute_overlay([{copy, In, ""} | Rest], _Vars, BaseDir, TargetDir);
  233. execute_overlay([{copy, In, Out} | Rest], Vars, BaseDir, TargetDir) ->
  234. InFile = rebar_templater:render(filename:join(BaseDir, In), Vars),
  235. OutFile = rebar_templater:render(filename:join(TargetDir, Out), Vars),
  236. case filelib:is_dir(InFile) of
  237. true ->
  238. ok;
  239. false ->
  240. ok = filelib:ensure_dir(OutFile)
  241. end,
  242. rebar_file_utils:cp_r([InFile], OutFile),
  243. execute_overlay(Rest, Vars, BaseDir, TargetDir);
  244. execute_overlay([{template_wildcard, Wildcard, OutDir} | Rest], Vars,
  245. BaseDir, TargetDir) ->
  246. %% Generate a series of {template, In, Out} instructions from the wildcard
  247. %% that will get processed per normal
  248. Ifun = fun(F, Acc0) ->
  249. [{template, F,
  250. filename:join(OutDir, filename:basename(F))} | Acc0]
  251. end,
  252. NewInstrs = lists:foldl(Ifun, Rest, filelib:wildcard(Wildcard, BaseDir)),
  253. case length(NewInstrs) =:= length(Rest) of
  254. true ->
  255. ?WARN("template_wildcard: ~s did not match any files!\n",
  256. [Wildcard]);
  257. false ->
  258. ok
  259. end,
  260. ?DEBUG("template_wildcard: ~s expanded to ~p\n", [Wildcard, NewInstrs]),
  261. execute_overlay(NewInstrs, Vars, BaseDir, TargetDir);
  262. execute_overlay([{template, In, Out} | Rest], Vars, BaseDir, TargetDir) ->
  263. InFile = rebar_templater:render(filename:join(BaseDir, In), Vars),
  264. {ok, InFileData} = file:read_file(InFile),
  265. OutFile = rebar_templater:render(filename:join(TargetDir, Out), Vars),
  266. ok = filelib:ensure_dir(OutFile),
  267. case file:write_file(OutFile, rebar_templater:render(InFileData, Vars)) of
  268. ok ->
  269. ok = apply_file_info(InFile, OutFile),
  270. ?DEBUG("Templated ~p\n", [OutFile]),
  271. execute_overlay(Rest, Vars, BaseDir, TargetDir);
  272. {error, Reason} ->
  273. ?ABORT("Failed to template ~p: ~p\n", [OutFile, Reason])
  274. end;
  275. execute_overlay([{create, Out, Contents} | Rest], Vars, BaseDir, TargetDir) ->
  276. OutFile = rebar_templater:render(filename:join(TargetDir, Out), Vars),
  277. ok = filelib:ensure_dir(OutFile),
  278. case file:write_file(OutFile, Contents) of
  279. ok ->
  280. ?DEBUG("Created ~p\n", [OutFile]),
  281. execute_overlay(Rest, Vars, BaseDir, TargetDir);
  282. {error, Reason} ->
  283. ?ABORT("Failed to create ~p: ~p\n", [OutFile, Reason])
  284. end;
  285. execute_overlay([{replace, Out, Regex, Replacement} | Rest],
  286. Vars, BaseDir, TargetDir) ->
  287. execute_overlay([{replace, Out, Regex, Replacement, []} | Rest],
  288. Vars, BaseDir, TargetDir);
  289. execute_overlay([{replace, Out, Regex, Replacement, Opts} | Rest],
  290. Vars, BaseDir, TargetDir) ->
  291. Filename = rebar_templater:render(filename:join(TargetDir, Out), Vars),
  292. {ok, OrigData} = file:read_file(Filename),
  293. Data = re:replace(OrigData, Regex,
  294. rebar_templater:render(Replacement, Vars),
  295. [global, {return, binary}] ++ Opts),
  296. case file:write_file(Filename, Data) of
  297. ok ->
  298. ?DEBUG("Edited ~s: s/~s/~s/\n", [Filename, Regex, Replacement]),
  299. execute_overlay(Rest, Vars, BaseDir, TargetDir);
  300. {error, Reason} ->
  301. ?ABORT("Failed to edit ~p: ~p\n", [Filename, Reason])
  302. end;
  303. execute_overlay([Other | _Rest], _Vars, _BaseDir, _TargetDir) ->
  304. {error, {unsupported_operation, Other}}.
  305. apply_file_info(InFile, OutFile) ->
  306. {ok, FileInfo} = file:read_file_info(InFile),
  307. ok = file:write_file_info(OutFile, FileInfo).
  308. create_RELEASES(TargetDir, RelName, RelVsn) ->
  309. ReleasesDir = filename:join(TargetDir, "releases"),
  310. RelFile = filename:join([ReleasesDir, RelVsn, RelName ++ ".rel"]),
  311. Apps = rebar_rel_utils:get_rel_apps(RelFile),
  312. TargetLib = filename:join(TargetDir,"lib"),
  313. AppDirs =
  314. [ {App, Vsn, TargetLib}
  315. || {App, Vsn} <- Apps,
  316. filelib:is_dir(
  317. filename:join(TargetLib,
  318. lists:concat([App, "-", Vsn]))) ],
  319. case release_handler:create_RELEASES(
  320. code:root_dir(),
  321. ReleasesDir,
  322. RelFile,
  323. AppDirs) of
  324. ok ->
  325. ok;
  326. {error, Reason} ->
  327. ?ABORT("Failed to create RELEASES file: ~p\n",
  328. [Reason])
  329. end.