Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

342 строки
13 KiB

15 лет назад
14 лет назад
14 лет назад
14 лет назад
15 лет назад
14 лет назад
14 лет назад
15 лет назад
14 лет назад
14 лет назад
15 лет назад
15 лет назад
15 лет назад
15 лет назад
14 лет назад
15 лет назад
15 лет назад
15 лет назад
14 лет назад
14 лет назад
  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_reltool).
  28. -export([generate/2,
  29. clean/2]).
  30. -include("rebar.hrl").
  31. -include_lib("reltool/src/reltool.hrl").
  32. -include_lib("kernel/include/file.hrl").
  33. %% ===================================================================
  34. %% Public API
  35. %% ===================================================================
  36. generate(Config, ReltoolFile) ->
  37. %% Make sure we have decent version of reltool available
  38. check_vsn(),
  39. %% Load the reltool configuration from the file
  40. ReltoolConfig = load_config(ReltoolFile),
  41. %% Spin up reltool server and load our config into it
  42. {ok, Server} = reltool:start_server([sys_tuple(ReltoolConfig)]),
  43. %% Do some validation of the reltool configuration; error messages out of
  44. %% reltool are still pretty cryptic
  45. validate_rel_apps(Server, sys_tuple(ReltoolConfig)),
  46. %% Finally, run reltool
  47. case catch(run_reltool(Server, Config, ReltoolConfig)) of
  48. ok ->
  49. ok;
  50. {error, failed} ->
  51. ?FAIL;
  52. Other2 ->
  53. ?ERROR("Unexpected error: ~p\n", [Other2]),
  54. ?FAIL
  55. end.
  56. clean(_Config, ReltoolFile) ->
  57. ReltoolConfig = load_config(ReltoolFile),
  58. TargetDir = target_dir(ReltoolConfig),
  59. rebar_file_utils:rm_rf(TargetDir),
  60. rebar_file_utils:delete_each(["reltool.spec"]).
  61. %% ===================================================================
  62. %% Internal functions
  63. %% ===================================================================
  64. check_vsn() ->
  65. case code:lib_dir(reltool) of
  66. {error, bad_name} ->
  67. ?ABORT("Reltool support requires the reltool application to be installed!", []);
  68. Path ->
  69. ReltoolVsn = filename:basename(Path),
  70. case ReltoolVsn < "reltool-0.5.2" of
  71. true ->
  72. ?ABORT("Reltool support requires at least reltool-0.5.2; this VM is using ~s\n",
  73. [ReltoolVsn]);
  74. false ->
  75. ok
  76. end
  77. end.
  78. %%
  79. %% Load terms from reltool.config
  80. %%
  81. load_config(ReltoolFile) ->
  82. case file:consult(ReltoolFile) of
  83. {ok, Terms} ->
  84. Terms;
  85. Other ->
  86. ?ABORT("Failed to load expected config from ~s: ~p\n", [ReltoolFile, Other])
  87. end.
  88. %%
  89. %% Look for the {sys, [...]} tuple in the reltool.config file. Without this present, we
  90. %% can't run reltool.
  91. %%
  92. sys_tuple(ReltoolConfig) ->
  93. case lists:keyfind(sys, 1, ReltoolConfig) of
  94. {sys, _} = SysTuple ->
  95. SysTuple;
  96. false ->
  97. ?ABORT("Failed to find {sys, [...]} tuple in reltool.config.", [])
  98. end.
  99. %%
  100. %% Look for {target_dir, TargetDir} in the reltool config file; if none is
  101. %% found, use the name of the release as the default target directory.
  102. %%
  103. target_dir(ReltoolConfig) ->
  104. case rebar_config:get_global(target_dir, undefined) of
  105. undefined ->
  106. case lists:keyfind(target_dir, 1, ReltoolConfig) of
  107. {target_dir, TargetDir} ->
  108. filename:absname(TargetDir);
  109. false ->
  110. {sys, SysInfo} = sys_tuple(ReltoolConfig),
  111. case lists:keyfind(rel, 1, SysInfo) of
  112. {rel, Name, _Vsn, _Apps} ->
  113. filename:absname(Name);
  114. false ->
  115. filename:absname("target")
  116. end
  117. end;
  118. TargetDir ->
  119. filename:absname(TargetDir)
  120. end.
  121. %%
  122. %% Look for overlay_vars file reference. The user can override this from the
  123. %% command line (i.e. globals), so we check there first and then fall back to
  124. %% what is present in the reltool.config file
  125. %%
  126. overlay_vars(ReltoolConfig) ->
  127. case rebar_config:get_global(overlay_vars, undefined) of
  128. undefined ->
  129. case lists:keyfind(overlay_vars, 1, ReltoolConfig) of
  130. {overlay_vars, File} ->
  131. File;
  132. false ->
  133. undefined
  134. end;
  135. File ->
  136. File
  137. end.
  138. validate_rel_apps(ReltoolServer, {sys, ReltoolConfig}) ->
  139. case lists:keyfind(rel, 1, ReltoolConfig) of
  140. false ->
  141. ok;
  142. {rel, _Name, _Vsn, Apps} ->
  143. %% Identify all the apps that do NOT exist, based on what's available
  144. %% from the reltool server
  145. Missing = lists:sort([App || App <- Apps,
  146. app_exists(App, ReltoolServer) == false]),
  147. case Missing of
  148. [] ->
  149. ok;
  150. _ ->
  151. ?ABORT("Apps in {rel, ...} section not found by reltool: ~p\n", [Missing])
  152. end;
  153. Rel ->
  154. %% Invalid release format!
  155. ?ABORT("Invalid {rel, ...} section in reltools.config: ~p\n", [Rel])
  156. end.
  157. app_exists(App, Server) when is_atom(App) ->
  158. case reltool_server:get_app(Server, App) of
  159. {ok, _} ->
  160. true;
  161. _ ->
  162. false
  163. end;
  164. app_exists(AppTuple, Server) when is_tuple(AppTuple) ->
  165. app_exists(element(1, AppTuple), Server).
  166. run_reltool(Server, _Config, ReltoolConfig) ->
  167. case reltool:get_target_spec(Server) of
  168. {ok, Spec} ->
  169. %% Pull the target dir and make sure it exists
  170. TargetDir = target_dir(ReltoolConfig),
  171. mk_target_dir(TargetDir),
  172. %% Dump the spec, if necessary
  173. dump_spec(Spec),
  174. %% Have reltool actually run
  175. case reltool:eval_target_spec(Spec, code:root_dir(), TargetDir) of
  176. ok ->
  177. ok;
  178. {error, Reason} ->
  179. ?ABORT("Failed to generate target from spec: ~p\n", [Reason])
  180. end,
  181. %% Initialize overlay vars with some basics (that can get overwritten)
  182. OverlayVars0 = [{erts_vsn, "erts-" ++ erlang:system_info(version)}],
  183. %% Load up any variables specified by overlay_vars
  184. OverlayVars = case overlay_vars(ReltoolConfig) of
  185. undefined ->
  186. dict:from_list(OverlayVars0);
  187. File ->
  188. case file:consult(File) of
  189. {ok, Terms} ->
  190. dict:from_list(OverlayVars0 ++ Terms);
  191. {error, Reason2} ->
  192. ?ABORT("Unable to load overlay_vars from ~s: ~p\n",
  193. [File, Reason2])
  194. end
  195. end,
  196. %% Finally, overlay the files specified by the overlay section
  197. case lists:keysearch(overlay, 1, ReltoolConfig) of
  198. {value, {overlay, Overlay}} when is_list(Overlay) ->
  199. execute_overlay(Overlay, OverlayVars, rebar_utils:get_cwd(), TargetDir);
  200. {value, _} ->
  201. ?ABORT("{overlay, [...]} entry in reltool.config must be a list.\n", []);
  202. false ->
  203. ?INFO("No {overlay, [...]} found in reltool.config.\n", [])
  204. end;
  205. {error, Reason} ->
  206. ?ABORT("Unable to generate spec: ~s\n", [Reason])
  207. end.
  208. mk_target_dir(TargetDir) ->
  209. case file:make_dir(TargetDir) of
  210. ok ->
  211. ok;
  212. {error, eexist} ->
  213. %% Output directory already exists; if force=1, wipe it out
  214. case rebar_config:get_global(force, "0") of
  215. "1" ->
  216. rebar_file_utils:rm_rf(TargetDir),
  217. ok = file:make_dir(TargetDir);
  218. _ ->
  219. ?ERROR("Release target directory ~p already exists!\n", [TargetDir]),
  220. ?FAIL
  221. end
  222. end.
  223. dump_spec(Spec) ->
  224. case rebar_config:get_global(dump_spec, "0") of
  225. "1" ->
  226. SpecBin = list_to_binary(io_lib:print(Spec, 1, 120, -1)),
  227. ok = file:write_file("reltool.spec", SpecBin);
  228. _ ->
  229. ok
  230. end.
  231. execute_overlay([], _Vars, _BaseDir, _TargetDir) ->
  232. ok;
  233. execute_overlay([{mkdir, Out} | Rest], Vars, BaseDir, TargetDir) ->
  234. OutFile = render(filename:join([TargetDir, Out, "dummy"]), Vars),
  235. ok = filelib:ensure_dir(OutFile),
  236. ?DEBUG("Created dir ~s\n", [filename:dirname(OutFile)]),
  237. execute_overlay(Rest, Vars, BaseDir, TargetDir);
  238. execute_overlay([{copy, In} | Rest], _Vars, BaseDir, TargetDir) ->
  239. execute_overlay([{copy, In, ""} | Rest], _Vars, BaseDir, TargetDir);
  240. execute_overlay([{copy, In, Out} | Rest], Vars, BaseDir, TargetDir) ->
  241. InFile = render(filename:join(BaseDir, In), Vars),
  242. OutFile = render(filename:join(TargetDir, Out), Vars),
  243. case filelib:is_dir(InFile) of
  244. true ->
  245. ok;
  246. false ->
  247. ok = filelib:ensure_dir(OutFile)
  248. end,
  249. rebar_file_utils:cp_r([InFile], OutFile),
  250. execute_overlay(Rest, Vars, BaseDir, TargetDir);
  251. execute_overlay([{template, In, Out} | Rest], Vars, BaseDir, TargetDir) ->
  252. InFile = render(filename:join(BaseDir, In), Vars),
  253. {ok, InFileData} = file:read_file(InFile),
  254. OutFile = render(filename:join(TargetDir, Out), Vars),
  255. ok = filelib:ensure_dir(OutFile),
  256. case file:write_file(OutFile, render(InFileData, Vars)) of
  257. ok ->
  258. ok = apply_file_info(InFile, OutFile),
  259. ?DEBUG("Templated ~p\n", [OutFile]),
  260. execute_overlay(Rest, Vars, BaseDir, TargetDir);
  261. {error, Reason} ->
  262. ?ABORT("Failed to template ~p: ~p\n", [OutFile, Reason])
  263. end;
  264. execute_overlay([{create, Out, Contents} | Rest], Vars, BaseDir, TargetDir) ->
  265. OutFile = render(filename:join(TargetDir, Out), Vars),
  266. ok = filelib:ensure_dir(OutFile),
  267. case file:write_file(OutFile, Contents) of
  268. ok ->
  269. ?DEBUG("Created ~p\n", [OutFile]),
  270. execute_overlay(Rest, Vars, BaseDir, TargetDir);
  271. {error, Reason} ->
  272. ?ABORT("Failed to create ~p: ~p\n", [OutFile, Reason])
  273. end;
  274. execute_overlay([{replace, Out, Regex, Replacement} | Rest],
  275. Vars, BaseDir, TargetDir) ->
  276. execute_overlay([{replace, Out, Regex, Replacement, []} | Rest], Vars, BaseDir, TargetDir);
  277. execute_overlay([{replace, Out, Regex, Replacement, Opts} | Rest],
  278. Vars, BaseDir, TargetDir) ->
  279. Filename = render(filename:join(TargetDir, Out), Vars),
  280. {ok, OrigData} = file:read_file(Filename),
  281. Data = re:replace(OrigData, Regex, Replacement, [global, {return, binary}] ++ Opts),
  282. case file:write_file(Filename, Data) of
  283. ok ->
  284. ?DEBUG("Edited ~s: s/~s/~s/\n", [Filename, Regex, Replacement]),
  285. execute_overlay(Rest, Vars, BaseDir, TargetDir);
  286. {error, Reason} ->
  287. ?ABORT("Failed to edit ~p: ~p\n", [Filename, Reason])
  288. end;
  289. execute_overlay([Other | _Rest], _Vars, _BaseDir, _TargetDir) ->
  290. {error, {unsupported_operation, Other}}.
  291. %%
  292. %% Render a binary to a string, using mustache and the specified context
  293. %%
  294. render(Bin, Context) ->
  295. ReOpts = [global, {return, list}],
  296. %% Be sure to escape any double-quotes before rendering...
  297. Str0 = re:replace(Bin, "\\\\", "\\\\\\", ReOpts),
  298. Str1 = re:replace(Str0, "\"", "\\\\\"", ReOpts),
  299. mustache:render(Str1, Context).
  300. apply_file_info(InFile, OutFile) ->
  301. {ok, FileInfo} = file:read_file_info(InFile),
  302. ok = file:write_file_info(OutFile, FileInfo).