Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

323 lignes
12 KiB

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