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 lines
14 KiB

14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 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_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.