Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

323 рядки
12 KiB

15 роки тому
15 роки тому
15 роки тому
15 роки тому
15 роки тому
15 роки тому
15 роки тому
15 роки тому
15 роки тому
15 роки тому
  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).