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.

489 lines
21 KiB

  1. -module(rebar_test_utils).
  2. -include_lib("common_test/include/ct.hrl").
  3. -include_lib("eunit/include/eunit.hrl").
  4. -export([init_rebar_state/1, init_rebar_state/2, run_and_check/4, check_results/3]).
  5. -export([expand_deps/2, flat_deps/1, top_level_deps/1]).
  6. -export([create_app/4, create_plugin/4, create_eunit_app/4, create_empty_app/4,
  7. create_config/2, create_config/3, package_app/3]).
  8. -export([create_random_name/1, create_random_vsn/0, write_src_file/2]).
  9. %% Pick the right random module
  10. -ifdef(rand_only).
  11. -define(random, rand).
  12. -else.
  13. -define(random, random).
  14. -endif.
  15. %%%%%%%%%%%%%%
  16. %%% Public %%%
  17. %%%%%%%%%%%%%%
  18. %% @doc {@see init_rebar_state/2}
  19. init_rebar_state(Config) -> init_rebar_state(Config, "apps_dir1_").
  20. %% @doc Takes a common test config and a name (string) and sets up
  21. %% a basic OTP app directory with a pre-configured rebar state to
  22. %% run tests with.
  23. init_rebar_state(Config, Name) ->
  24. application:load(rebar),
  25. DataDir = ?config(priv_dir, Config),
  26. AppsDir = filename:join([DataDir, create_random_name(Name)]),
  27. CheckoutsDir = filename:join([AppsDir, "_checkouts"]),
  28. ok = ec_file:mkdir_p(AppsDir),
  29. ok = ec_file:mkdir_p(CheckoutsDir),
  30. Verbosity = rebar3:log_level(),
  31. rebar_log:init(command_line, Verbosity),
  32. GlobalDir = filename:join([DataDir, "cache"]),
  33. State = rebar_state:new([{base_dir, filename:join([AppsDir, "_build"])}
  34. ,{global_rebar_dir, GlobalDir}
  35. ,{root_dir, AppsDir}]),
  36. [{apps, AppsDir}, {checkouts, CheckoutsDir}, {state, State} | Config].
  37. %% @doc Takes common test config, a rebar config ([] if empty), a command to
  38. %% run ("install_deps", "compile", etc.), and a list of expected applications
  39. %% and/or dependencies to be present, and verifies whether they are all in
  40. %% place.
  41. %%
  42. %% The expectation list takes elements of the form:
  43. %% - `{app, Name :: string()}': checks that the app is properly built.
  44. %% - `{dep, Name :: string()}': checks that the dependency has been fetched.
  45. %% Ignores the build status of the dependency.
  46. %% - `{dep, Name :: string(), Vsn :: string()}': checks that the dependency
  47. %% has been fetched, and that a given version has been chosen. Useful to
  48. %% test for conflict resolution. Also ignores the build status of the
  49. %% dependency.
  50. %%
  51. %% This function assumes `init_rebar_state/1-2' has run before, in order to
  52. %% fetch the `apps' and `state' values from the CT config.
  53. run_and_check(Config, RebarConfig, Command, Expect) ->
  54. %% Assumes init_rebar_state has run first
  55. AppDir = ?config(apps, Config),
  56. State = ?config(state, Config),
  57. try
  58. Res = rebar3:run(rebar_state:new(State, RebarConfig, AppDir), Command),
  59. case Expect of
  60. {error, Reason} ->
  61. ?assertEqual({error, Reason}, Res);
  62. {ok, Expected} ->
  63. {ok, _} = Res,
  64. check_results(AppDir, Expected, "*"),
  65. Res;
  66. {ok, Expected, ProfileRun} ->
  67. {ok, _} = Res,
  68. check_results(AppDir, Expected, ProfileRun),
  69. Res;
  70. return ->
  71. Res
  72. end
  73. catch
  74. rebar_abort when Expect =:= rebar_abort -> rebar_abort
  75. end.
  76. %% @doc Creates a dummy application including:
  77. %% - src/<file>.erl
  78. %% - src/<file>.app.src
  79. %% And returns a `rebar_app_info' object.
  80. create_app(AppDir, Name, Vsn, Deps) ->
  81. write_src_file(AppDir, Name ++ ".erl"),
  82. write_src_file(AppDir, "not_a_real_src_" ++ Name ++ ".erl"),
  83. write_app_src_file(AppDir, Name, Vsn, Deps),
  84. rebar_app_info:new(Name, Vsn, AppDir, Deps).
  85. %% @doc Creates a dummy plugin including:
  86. %% - src/<file>.erl
  87. %% - src/<file>.app.src
  88. %% And returns a `rebar_app_info' object.
  89. create_plugin(AppDir, Name, Vsn, Deps) ->
  90. write_plugin_file(AppDir, Name ++ ".erl"),
  91. write_src_file(AppDir, "not_a_real_src_" ++ Name ++ ".erl"),
  92. write_app_src_file(AppDir, Name, Vsn, Deps),
  93. rebar_app_info:new(Name, Vsn, AppDir, Deps).
  94. %% @doc Creates a dummy application including:
  95. %% - src/<file>.erl
  96. %% - src/<file>.app.src
  97. %% - test/<file>_tests.erl
  98. %% And returns a `rebar_app_info' object.
  99. create_eunit_app(AppDir, Name, Vsn, Deps) ->
  100. write_eunitized_src_file(AppDir, Name),
  101. write_eunit_suite_file(AppDir, Name),
  102. write_app_src_file(AppDir, Name, Vsn, Deps),
  103. rebar_app_info:new(Name, Vsn, AppDir, Deps).
  104. %% @doc Creates a dummy application including:
  105. %% - ebin/<file>.app
  106. %% And returns a `rebar_app_info' object.
  107. create_empty_app(AppDir, Name, Vsn, Deps) ->
  108. write_app_file(AppDir, Name, Vsn, Deps),
  109. rebar_app_info:new(Name, Vsn, AppDir, Deps).
  110. %% @doc Creates a rebar.config file. The function accepts a list of terms,
  111. %% each of which will be dumped as a consult file. For example, the list
  112. %% `[a, b, c]' will return the consult file `a. b. c.'.
  113. create_config(AppDir, Contents) ->
  114. ConfFilename = filename:join([AppDir, "rebar.config"]),
  115. create_config(AppDir, ConfFilename, Contents).
  116. create_config(_AppDir, ConfFilename, Contents) ->
  117. ok = filelib:ensure_dir(ConfFilename),
  118. Config = lists:flatten([io_lib:fwrite("~p.~n", [Term]) || Term <- Contents]),
  119. ok = ec_file:write(ConfFilename, Config),
  120. ConfFilename.
  121. %% @doc Util to create a random variation of a given name.
  122. create_random_name(Name) ->
  123. random_seed(),
  124. Name ++ erlang:integer_to_list(?random:uniform(1000000)).
  125. %% @doc Util to create a random variation of a given version.
  126. create_random_vsn() ->
  127. random_seed(),
  128. lists:flatten([erlang:integer_to_list(?random:uniform(100)),
  129. ".", erlang:integer_to_list(?random:uniform(100)),
  130. ".", erlang:integer_to_list(?random:uniform(100))]).
  131. -ifdef(rand_only).
  132. random_seed() ->
  133. %% the rand module self-seeds
  134. ok.
  135. -else.
  136. random_seed() ->
  137. <<A:32, B:32, C:32>> = crypto:rand_bytes(12),
  138. random:seed({A,B,C}).
  139. -endif.
  140. expand_deps(_, []) -> [];
  141. expand_deps(git, [{Name, Deps} | Rest]) ->
  142. Dep = {Name, ".*", {git, "https://example.org/user/"++Name++".git", "master"}},
  143. [{Dep, expand_deps(git, Deps)} | expand_deps(git, Rest)];
  144. expand_deps(git, [{Name, Vsn, Deps} | Rest]) ->
  145. Dep = {Name, Vsn, {git, "https://example.org/user/"++Name++".git", {tag, Vsn}}},
  146. [{Dep, expand_deps(git, Deps)} | expand_deps(git, Rest)];
  147. expand_deps(pkg, [{Name, Deps} | Rest]) ->
  148. Dep = {pkg, Name, "0.0.0", undefined},
  149. [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)];
  150. expand_deps(pkg, [{Name, Vsn, Deps} | Rest]) ->
  151. Dep = {pkg, Name, Vsn, undefined},
  152. [{Dep, expand_deps(pkg, Deps)} | expand_deps(pkg, Rest)];
  153. expand_deps(mixed, [{Name, Deps} | Rest]) ->
  154. Dep = if hd(Name) >= $a, hd(Name) =< $z ->
  155. {pkg, string:to_upper(Name), "0.0.0", undefined}
  156. ; hd(Name) >= $A, hd(Name) =< $Z ->
  157. {Name, ".*", {git, "https://example.org/user/"++Name++".git", "master"}}
  158. end,
  159. [{Dep, expand_deps(mixed, Deps)} | expand_deps(mixed, Rest)];
  160. expand_deps(mixed, [{Name, Vsn, Deps} | Rest]) ->
  161. Dep = if hd(Name) >= $a, hd(Name) =< $z ->
  162. {pkg, string:to_upper(Name), Vsn, undefined}
  163. ; hd(Name) >= $A, hd(Name) =< $Z ->
  164. {Name, Vsn, {git, "https://example.org/user/"++Name++".git", {tag, Vsn}}}
  165. end,
  166. [{Dep, expand_deps(mixed, Deps)} | expand_deps(mixed, Rest)].
  167. %% Source deps can depend on both source and package dependencies;
  168. %% package deps can only depend on package deps.
  169. %% For things to work we have to go down the dep tree and find all
  170. %% lineages of pkg deps and return them, whereas the source deps
  171. %% can be left as is.
  172. flat_deps(Deps) -> flat_deps(Deps, [], []).
  173. flat_deps([], Src, Pkg) -> {Src, Pkg};
  174. flat_deps([{{pkg, Name, Vsn, undefined}, PkgDeps} | Rest], Src, Pkg) ->
  175. Current = {{iolist_to_binary(Name), iolist_to_binary(Vsn)},
  176. top_level_deps(PkgDeps)},
  177. {[], FlatPkgDeps} = flat_deps(PkgDeps),
  178. flat_deps(Rest,
  179. Src,
  180. Pkg ++ [Current | FlatPkgDeps]);
  181. flat_deps([{{Name,_Vsn,Ref}, Deps} | Rest], Src, Pkg) ->
  182. Current = {{Name,vsn_from_ref(Ref)}, top_level_deps(Deps)},
  183. {FlatDeps, FlatPkgDeps} = flat_deps(Deps),
  184. flat_deps(Rest,
  185. Src ++ [Current | FlatDeps],
  186. Pkg ++ FlatPkgDeps).
  187. vsn_from_ref({git, _, {_, Vsn}}) -> Vsn;
  188. vsn_from_ref({git, _, Vsn}) -> Vsn.
  189. top_level_deps([]) -> [];
  190. top_level_deps([{{pkg, Name, Vsn, undefined}, _} | Deps]) ->
  191. [{list_to_atom(Name), Vsn} | top_level_deps(Deps)];
  192. top_level_deps([{{Name, Vsn, Ref}, _} | Deps]) ->
  193. [{list_to_atom(Name), Vsn, Ref} | top_level_deps(Deps)].
  194. %%%%%%%%%%%%%%%
  195. %%% Helpers %%%
  196. %%%%%%%%%%%%%%%
  197. check_results(AppDir, Expected, ProfileRun) ->
  198. BuildDirs = filelib:wildcard(filename:join([AppDir, "_build", ProfileRun, "lib", "*"])),
  199. PluginDirs = filelib:wildcard(filename:join([AppDir, "_build", ProfileRun, "plugins", "*"])),
  200. GlobalPluginDirs = filelib:wildcard(filename:join([AppDir, "global", "plugins", "*"])),
  201. CheckoutsDir = filename:join([AppDir, "_checkouts", "*"]),
  202. LockFile = filename:join([AppDir, "rebar.lock"]),
  203. Locks = lists:flatten(rebar_config:consult_lock_file(LockFile)),
  204. InvalidApps = rebar_app_discover:find_apps(BuildDirs, invalid),
  205. ValidApps = rebar_app_discover:find_apps(BuildDirs, valid),
  206. InvalidDepsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- InvalidApps],
  207. ValidDepsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- ValidApps],
  208. Deps = rebar_app_discover:find_apps(BuildDirs, all),
  209. DepsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- Deps],
  210. Checkouts = rebar_app_discover:find_apps([CheckoutsDir], all),
  211. CheckoutsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- Checkouts],
  212. Plugins = rebar_app_discover:find_apps(PluginDirs, all),
  213. PluginsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- Plugins],
  214. GlobalPlugins = rebar_app_discover:find_apps(GlobalPluginDirs, all),
  215. GlobalPluginsNames = [{ec_cnv:to_list(rebar_app_info:name(App)), App} || App <- GlobalPlugins],
  216. lists:foreach(
  217. fun({app, Name}) ->
  218. ct:pal("App Name: ~p", [Name]),
  219. case lists:keyfind(Name, 1, DepsNames) of
  220. false ->
  221. error({app_not_found, Name});
  222. {Name, _App} ->
  223. ok
  224. end
  225. ; ({app, Name, invalid}) ->
  226. ct:pal("Invalid Name: ~p", [Name]),
  227. case lists:keyfind(Name, 1, InvalidDepsNames) of
  228. false ->
  229. error({app_not_found, Name});
  230. {Name, _App} ->
  231. ok
  232. end
  233. ; ({app, Name, valid}) ->
  234. ct:pal("Valid Name: ~p", [Name]),
  235. case lists:keyfind(Name, 1, ValidDepsNames) of
  236. false ->
  237. error({app_not_found, Name});
  238. {Name, _App} ->
  239. ok
  240. end
  241. ; ({dep_not_exist, Name}) ->
  242. ct:pal("App Not Exist Name: ~p", [Name]),
  243. case lists:keyfind(Name, 1, DepsNames) of
  244. false ->
  245. ok;
  246. {Name, _App} ->
  247. error({app_found, Name})
  248. end
  249. ; ({checkout, Name}) ->
  250. ct:pal("Checkout Name: ~p", [Name]),
  251. ?assertNotEqual(false, lists:keyfind(Name, 1, CheckoutsNames))
  252. ; ({dep, Name}) ->
  253. ct:pal("Dep Name: ~p", [Name]),
  254. ?assertNotEqual(false, lists:keyfind(Name, 1, DepsNames))
  255. ; ({dep, Name, Vsn}) ->
  256. ct:pal("Dep Name: ~p, Vsn: ~p", [Name, Vsn]),
  257. case lists:keyfind(Name, 1, DepsNames) of
  258. false ->
  259. error({dep_not_found, Name});
  260. {Name, App} ->
  261. ?assertEqual(iolist_to_binary(Vsn),
  262. iolist_to_binary(rebar_app_info:original_vsn(App)))
  263. end
  264. ; ({plugin, Name}) ->
  265. ct:pal("Plugin Name: ~p", [Name]),
  266. ?assertNotEqual(false, lists:keyfind(Name, 1, PluginsNames))
  267. ; ({plugin, Name, Vsn}) ->
  268. ct:pal("Plugin Name: ~p, Vsn: ~p", [Name, Vsn]),
  269. case lists:keyfind(Name, 1, PluginsNames) of
  270. false ->
  271. error({plugin_not_found, Name});
  272. {Name, App} ->
  273. ?assertEqual(iolist_to_binary(Vsn),
  274. iolist_to_binary(rebar_app_info:original_vsn(App)))
  275. end
  276. ; ({global_plugin, Name}) ->
  277. ct:pal("Global Plugin Name: ~p", [Name]),
  278. ?assertNotEqual(false, lists:keyfind(Name, 1, GlobalPluginsNames))
  279. ; ({global_plugin, Name, Vsn}) ->
  280. ct:pal("Global Plugin Name: ~p, Vsn: ~p", [Name, Vsn]),
  281. case lists:keyfind(Name, 1, GlobalPluginsNames) of
  282. false ->
  283. error({global_plugin_not_found, Name});
  284. {Name, App} ->
  285. ?assertEqual(iolist_to_binary(Vsn),
  286. iolist_to_binary(rebar_app_info:original_vsn(App)))
  287. end
  288. ; ({lock, Name}) ->
  289. ct:pal("Lock Name: ~p", [Name]),
  290. ?assertNotEqual(false, lists:keyfind(iolist_to_binary(Name), 1, Locks))
  291. ; ({lock, Name, Vsn}) ->
  292. ct:pal("Lock Name: ~p, Vsn: ~p", [Name, Vsn]),
  293. case lists:keyfind(iolist_to_binary(Name), 1, Locks) of
  294. false ->
  295. error({lock_not_found, Name});
  296. {_LockName, {pkg, _, LockVsn, Hash}, _} ->
  297. ?assertEqual(iolist_to_binary(Vsn),
  298. iolist_to_binary(LockVsn)),
  299. ?assertNotEqual(undefined, Hash);
  300. {_LockName, {_, _, {ref, LockVsn}}, _} ->
  301. ?assertEqual(iolist_to_binary(Vsn),
  302. iolist_to_binary(LockVsn))
  303. end
  304. ; ({lock, pkg, Name, Vsn}) ->
  305. ct:pal("Pkg Lock Name: ~p, Vsn: ~p", [Name, Vsn]),
  306. case lists:keyfind(iolist_to_binary(Name), 1, Locks) of
  307. false ->
  308. error({lock_not_found, Name});
  309. {_LockName, {pkg, _, LockVsn, Hash}, _} ->
  310. ?assertEqual(iolist_to_binary(Vsn),
  311. iolist_to_binary(LockVsn)),
  312. ?assertNotEqual(undefined, Hash);
  313. {_LockName, {_, _, {ref, LockVsn}}, _} ->
  314. error({source_lock, {Name, LockVsn}})
  315. end
  316. ; ({lock, src, Name, Vsn}) ->
  317. ct:pal("Src Lock Name: ~p, Vsn: ~p", [Name, Vsn]),
  318. case lists:keyfind(iolist_to_binary(Name), 1, Locks) of
  319. false ->
  320. error({lock_not_found, Name});
  321. {_LockName, {pkg, _, LockVsn, _}, _} ->
  322. error({pkg_lock, {Name, LockVsn}});
  323. {_LockName, {_, _, {ref, LockVsn}}, _} ->
  324. ?assertEqual(iolist_to_binary(Vsn),
  325. iolist_to_binary(LockVsn))
  326. end
  327. ; ({release, Name, Vsn, ExpectedDevMode}) ->
  328. ct:pal("Release: ~p-~s", [Name, Vsn]),
  329. {ok, Cwd} = file:get_cwd(),
  330. try
  331. file:set_cwd(AppDir),
  332. [ReleaseDir] = filelib:wildcard(filename:join([AppDir, "_build", "*", "rel"])),
  333. RelxState = rlx_state:new("", [], []),
  334. RelxState1 = rlx_state:base_output_dir(RelxState, ReleaseDir),
  335. {ok, RelxState2} = rlx_prv_app_discover:do(RelxState1),
  336. {ok, RelxState3} = rlx_prv_rel_discover:do(RelxState2),
  337. LibDir = filename:join([ReleaseDir, Name, "lib"]),
  338. {ok, RelLibs} = rebar_utils:list_dir(LibDir),
  339. IsSymLinkFun =
  340. fun(X) ->
  341. ec_file:is_symlink(filename:join(LibDir, X))
  342. end,
  343. DevMode = lists:all(IsSymLinkFun, RelLibs),
  344. ?assertEqual(ExpectedDevMode, DevMode),
  345. %% throws not_found if it doesn't exist
  346. rlx_state:get_realized_release(RelxState3, Name, Vsn)
  347. catch
  348. _ ->
  349. ct:fail(release_not_found)
  350. after
  351. file:set_cwd(Cwd)
  352. end
  353. ; ({tar, Name, Vsn}) ->
  354. ct:pal("Tarball: ~s-~s", [Name, Vsn]),
  355. Tarball = filename:join([AppDir, "_build", "rel", Name, Name++"-"++Vsn++".tar.gz"]),
  356. ?assertNotEqual([], filelib:is_file(Tarball))
  357. ; ({file, Filename}) ->
  358. ct:pal("Filename: ~s", [Filename]),
  359. ?assert(filelib:is_file(Filename))
  360. ; ({dir, Dirname}) ->
  361. ct:pal("Directory: ~s", [Dirname]),
  362. ?assert(filelib:is_dir(Dirname))
  363. end, Expected).
  364. write_plugin_file(Dir, Name) ->
  365. Erl = filename:join([Dir, "src", Name]),
  366. ok = filelib:ensure_dir(Erl),
  367. ok = ec_file:write(Erl, plugin_src_file(Name)).
  368. write_src_file(Dir, Name) ->
  369. Erl = filename:join([Dir, "src", Name]),
  370. ok = filelib:ensure_dir(Erl),
  371. ok = ec_file:write(Erl, erl_src_file(Name)).
  372. write_eunitized_src_file(Dir, Name) ->
  373. Erl = filename:join([Dir, "src", "not_a_real_src_" ++ Name ++ ".erl"]),
  374. ok = filelib:ensure_dir(Erl),
  375. ok = ec_file:write(Erl, erl_eunitized_src_file("not_a_real_src_" ++ Name ++ ".erl")).
  376. write_eunit_suite_file(Dir, Name) ->
  377. Erl = filename:join([Dir, "test", "not_a_real_src_" ++ Name ++ "_tests.erl"]),
  378. ok = filelib:ensure_dir(Erl),
  379. ok = ec_file:write(Erl, erl_eunit_suite_file("not_a_real_src_" ++ Name ++ ".erl")).
  380. write_app_file(Dir, Name, Version, Deps) ->
  381. Filename = filename:join([Dir, "ebin", Name ++ ".app"]),
  382. ok = filelib:ensure_dir(Filename),
  383. ok = ec_file:write_term(Filename, get_app_metadata(ec_cnv:to_list(Name), Version, Deps)).
  384. write_app_src_file(Dir, Name, Version, Deps) ->
  385. Filename = filename:join([Dir, "src", Name ++ ".app.src"]),
  386. ok = filelib:ensure_dir(Filename),
  387. ok = ec_file:write_term(Filename, get_app_metadata(ec_cnv:to_list(Name), Version, Deps)).
  388. erl_src_file(Name) ->
  389. io_lib:format("-module('~s').\n"
  390. "-export([main/0]).\n"
  391. "main() -> ok.\n", [filename:basename(Name, ".erl")]).
  392. plugin_src_file(Name) ->
  393. io_lib:format("-module('~s').\n"
  394. "-export([init/1]).\n"
  395. "init(State) -> \n"
  396. "Provider = providers:create([\n"
  397. "{name, '~s'},\n"
  398. "{module, '~s'}\n"
  399. "]),\n"
  400. "{ok, rebar_state:add_provider(State, Provider)}.\n", [filename:basename(Name, ".erl"),
  401. filename:basename(Name, ".erl"),
  402. filename:basename(Name, ".erl")]).
  403. erl_eunitized_src_file(Name) ->
  404. io_lib:format("-module('~s').\n"
  405. "-export([main/0]).\n"
  406. "main() -> ok.\n"
  407. "-ifdef(TEST).\n"
  408. "-include_lib(\"eunit/include/eunit.hrl\").\n"
  409. "some_test_() -> ?_assertEqual(ok, main()).\n"
  410. "-endif.\n", [filename:basename(Name, ".erl")]).
  411. erl_eunit_suite_file(Name) ->
  412. BaseName = filename:basename(Name, ".erl"),
  413. io_lib:format("-module('~s_tests').\n"
  414. "-compile(export_all).\n"
  415. "-ifndef(some_define).\n"
  416. "-define(some_define, false).\n"
  417. "-endif.\n"
  418. "-ifdef(TEST).\n"
  419. "-include_lib(\"eunit/include/eunit.hrl\").\n"
  420. "some_test_() -> ?_assertEqual(ok, ~s:main()).\n"
  421. "define_test_() -> ?_assertEqual(true, ?some_define).\n"
  422. "-endif.\n", [BaseName, BaseName]).
  423. get_app_metadata(Name, Vsn, Deps) ->
  424. {application, erlang:list_to_atom(Name),
  425. [{description, ""},
  426. {vsn, Vsn},
  427. {modules, []},
  428. {included_applications, []},
  429. {registered, []},
  430. {applications, Deps}]}.
  431. package_app(AppDir, DestDir, PkgName) ->
  432. Name = PkgName++".tar",
  433. {ok, Fs} = rebar_utils:list_dir(AppDir),
  434. ok = erl_tar:create(filename:join(DestDir, "contents.tar.gz"),
  435. lists:zip(Fs, [filename:join(AppDir,F) || F <- Fs]),
  436. [compressed]),
  437. ok = file:write_file(filename:join(DestDir, "metadata.config"), "who cares"),
  438. ok = file:write_file(filename:join(DestDir, "VERSION"), "3"),
  439. {ok, Contents} = file:read_file(filename:join(DestDir, "contents.tar.gz")),
  440. Blob = <<"3who cares", Contents/binary>>,
  441. <<X:256/big-unsigned>> = crypto:hash(sha256, Blob),
  442. BinChecksum = list_to_binary(string:to_upper(lists:flatten(io_lib:format("~64.16.0b", [X])))),
  443. ok = file:write_file(filename:join(DestDir, "CHECKSUM"), BinChecksum),
  444. PkgFiles = ["contents.tar.gz", "VERSION", "metadata.config", "CHECKSUM"],
  445. Archive = filename:join(DestDir, Name),
  446. ok = erl_tar:create(Archive,
  447. lists:zip(PkgFiles, [filename:join(DestDir,F) || F <- PkgFiles])),
  448. {ok, BinFull} = file:read_file(Archive),
  449. <<E:128/big-unsigned-integer>> = crypto:hash(md5, BinFull),
  450. Etag = string:to_lower(lists:flatten(io_lib:format("~32.16.0b", [E]))),
  451. {BinChecksum, Etag}.