emqx_plugrel.erl 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. -module(emqx_plugrel).
  2. -export([init/1, do/1, format_error/1]).
  3. -define(LOG(LEVEL, FORMAT, ARGS),
  4. rebar_api:LEVEL("[emqx_plugrel] " ++ FORMAT, ARGS)).
  5. -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
  6. init(State) ->
  7. Provider = providers:create([
  8. {namespace, emqx_plugrel},
  9. {name, tar}, % The 'user friendly' name of the task
  10. {module, ?MODULE}, % The module implementation of the task
  11. {bare, true}, % The task can be run by the user, always true
  12. {deps, [{default, release}]}, % The list of dependencies
  13. {example, "rebar3 emqx_plugrel tar"}, % How to use the plugin
  14. {opts, [emqx_plugrel]}, % list of options understood by the plugin
  15. {short_desc, "EMQ X plugin zip package"},
  16. {desc, "A rebar3 plugin that helps to release a zip package for EMQ X plugin"}
  17. ]),
  18. {ok, rebar_state:add_provider(State, Provider)}.
  19. do(State) ->
  20. Opts = rebar_state:opts(State),
  21. Relx = rebar_opts:get(Opts, relx),
  22. PluginInfo = rebar_opts:get(Opts, emqx_plugrel),
  23. case lists:keyfind(release, 1, Relx) of
  24. {release, {Name, Version}, Apps} ->
  25. Info = collect_info(PluginInfo, Name, Version, Apps, State),
  26. ok = make_tar(Info);
  27. false ->
  28. ?LOG(error, "relx_config_not_found", []),
  29. error(relx_config_not_found)
  30. end,
  31. {ok, State}.
  32. -spec format_error(any()) -> iolist().
  33. format_error(Reason) ->
  34. io_lib:format("~p", [Reason]).
  35. collect_info(PluginInfo, Name, Version, Apps, State) ->
  36. AppsWithVsn = lists:map(fun(App) -> resolve_vsn(App, State) end, Apps),
  37. Info = info_map(PluginInfo),
  38. MoreInfo = #{ name => bin(atom_to_list(Name))
  39. , rel_vsn => bin(Version)
  40. , rel_apps => AppsWithVsn
  41. , build_time => now_time()
  42. },
  43. maps:merge(Info, MoreInfo).
  44. now_time() ->
  45. bin(calendar:system_time_to_rfc3339(erlang:system_time(second))).
  46. %% Find app vsn from compiled .app files
  47. %% such info is technically available from within rebar State,
  48. %% however that requires some deep knowledge of rebar3 internals
  49. %% Returns a list of app names with -<vsn> suffix in binary() string format.
  50. resolve_vsn(App, _State) ->
  51. AppStr = atom_to_list(App),
  52. AppFile = filename:join(["_build", "default", "lib", App, "ebin", bin([AppStr, ".app"])]),
  53. case file:consult(AppFile) of
  54. {ok, AppInfo} ->
  55. bin(AppStr ++ "-" ++ get_vsn(AppInfo));
  56. {error, Reason} ->
  57. ?LOG(error, "failed_to_read_app_vsn ~s ~p", [AppFile, Reason]),
  58. error({failed_to_read_app_vsn, AppFile, Reason})
  59. end.
  60. get_vsn([{application, _Name, Info}]) ->
  61. {vsn, Vsn} = lists:keyfind(vsn, 1, Info),
  62. Vsn.
  63. make_tar(#{name := Name, rel_vsn := Vsn, rel_apps := Apps} = Info) ->
  64. Dir = filename:join(["_build", ?MODULE]),
  65. NameWithVsn = binary_to_list(bin([Name, "-", Vsn])),
  66. %% write info file
  67. InfoFile = filename:join([Dir, NameWithVsn ++ ".json"]),
  68. ok = filelib:ensure_dir(InfoFile),
  69. ok = file:write_file(InfoFile, jsx:encode(Info, [space, {indent, 4}])),
  70. %% copy apps to lib dir
  71. LibDir = filename:join([Dir, lib]),
  72. ok = rebar_file_utils:rm_rf(LibDir),
  73. ok = filelib:ensure_dir(filename:join([LibDir, "foo"])),
  74. Sources = lists:map(fun(App) -> filename:join(["_build", "default", "rel", Name, "lib", App]) end, Apps),
  75. ok = rebar_file_utils:cp_r(Sources, LibDir),
  76. {ok, OriginalCwd} = file:get_cwd(),
  77. ok = file:set_cwd(Dir),
  78. try
  79. do_make_tar(Dir, NameWithVsn)
  80. after
  81. file:set_cwd(OriginalCwd)
  82. end,
  83. ok = file:delete(InfoFile).
  84. do_make_tar(Cwd, NameWithVsn) ->
  85. Files = filelib:wildcard("lib/**"),
  86. TarFile = NameWithVsn ++ ".tar.gz",
  87. FullName = filename:join([Cwd, TarFile]),
  88. ?LOG(info, "creating ~s", [FullName]),
  89. ok = erl_tar:create(TarFile, [NameWithVsn ++ ".json"| Files], [compressed]),
  90. {ok, Bin} = file:read_file(TarFile),
  91. Sha = bin2hexstr(crypto:hash(sha256, Bin)),
  92. ok = file:write_file(NameWithVsn ++ ".sha256", Sha).
  93. bin(X) -> iolist_to_binary(X).
  94. str_list(L) -> lists:map(fun bin/1, L).
  95. info_field(authors, Authors) -> str_list(Authors);
  96. info_field(builder, Builder) -> info_map(Builder);
  97. info_field(functionality, Fs) -> str_list(Fs);
  98. info_field(compatibility, Cs) -> info_map(Cs);
  99. info_field(_, Value) -> bin(Value).
  100. info_map(InfoList) ->
  101. maps:from_list(lists:map(fun({K, V}) -> {K, info_field(K, V)} end, InfoList)).
  102. bin2hexstr(B) when is_binary(B) ->
  103. << <<(int2hexchar(H)), (int2hexchar(L))>> || <<H:4, L:4>> <= B>>.
  104. int2hexchar(I) when I >= 0 andalso I < 10 -> I + $0;
  105. int2hexchar(I) -> I - 10 + $a.