Преглед изворни кода

feat: create tar.gz for release apps

Zaiming (Stone) Shi пре 4 година
родитељ
комит
cab90c1465
2 измењених фајлова са 100 додато и 13 уклоњено
  1. 1 1
      rebar.config
  2. 99 12
      src/emqx_plugrel.erl

+ 1 - 1
rebar.config

@@ -1,2 +1,2 @@
 {erl_opts, [debug_info]}.
-{deps, []}.
+{deps, [{jsx, "3.1.0"}]}.

+ 99 - 12
src/emqx_plugrel.erl

@@ -5,31 +5,118 @@
 -define(LOG(LEVEL, FORMAT, ARGS),
         rebar_api:LEVEL("[emqx_plugrel] " ++ FORMAT, ARGS)).
 
--define(supported_pre_vsns(_CurrVsn), <<".*">>).
-
--define(PROVIDER, zip).
--define(DEPS, [release]).
-
 -spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
 init(State) ->
     Provider = providers:create([
             {namespace, emqx_plugrel},
-            {name, ?PROVIDER},            % The 'user friendly' name of the task
-            {module, ?MODULE},            % The module implementation of the task
-            {bare, true},                 % The task can be run by the user, always true
-            {deps, ?DEPS},                % The list of dependencies
-            {example, "rebar3 emqx_plugrel zip"}, % How to use the plugin
-            {opts, []},                   % list of options understood by the plugin
+            {name, tar}, % The 'user friendly' name of the task
+            {module, ?MODULE}, % The module implementation of the task
+            {bare, true}, % The task can be run by the user, always true
+            {deps, [{default, release}]},  % The list of dependencies
+            {example, "rebar3 emqx_plugrel tar"}, % How to use the plugin
+            {opts, [emqx_plugrel]}, % list of options understood by the plugin
             {short_desc, "EMQ X plugin zip package"},
             {desc, "A rebar3 plugin that helps to release a zip package for EMQ X plugin"}
     ]),
     {ok, rebar_state:add_provider(State, Provider)}.
 
 do(State) ->
-    io:format(user, "~p\n", [State]),
+    Opts = rebar_state:opts(State),
+    Relx = rebar_opts:get(Opts, relx),
+    PluginInfo = rebar_opts:get(Opts, emqx_plugrel),
+    case lists:keyfind(release, 1, Relx) of
+        {release, {Name, Version}, Apps} ->
+            Info = collect_info(PluginInfo, Name, Version, Apps, State),
+            ok = make_tar(Info);
+        false ->
+            ?LOG(error, "relx_config_not_found", []),
+            error(relx_config_not_found)
+    end,
     {ok, State}.
 
 -spec format_error(any()) ->  iolist().
 format_error(Reason) ->
     io_lib:format("~p", [Reason]).
 
+collect_info(PluginInfo, Name, Version, Apps, State) ->
+    AppsWithVsn = lists:map(fun(App) -> resolve_vsn(App, State) end, Apps),
+    Info = info_map(PluginInfo),
+    MoreInfo = #{ name => bin(atom_to_list(Name))
+                , rel_vsn => bin(Version)
+                , rel_apps => AppsWithVsn
+                , build_time => now_time()
+                },
+    maps:merge(Info, MoreInfo).
+
+now_time() ->
+    bin(calendar:system_time_to_rfc3339(erlang:system_time(second))).
+
+%% Find app vsn from compiled .app files
+%% such info is technically available from within rebar State,
+%% however that requires some deep knowledge of rebar3 internals
+%% Returns a list of app names with -<vsn> suffix in binary() string format.
+resolve_vsn(App, _State) ->
+    AppStr = atom_to_list(App),
+    AppFile = filename:join(["_build", "default", "lib", App, "ebin", bin([AppStr, ".app"])]),
+    case file:consult(AppFile) of
+        {ok, AppInfo} ->
+            bin(AppStr ++ "-" ++ get_vsn(AppInfo));
+        {error, Reason} ->
+            ?LOG(error, "failed_to_read_app_vsn ~s ~p", [AppFile, Reason]),
+            error({failed_to_read_app_vsn, AppFile, Reason})
+    end.
+
+get_vsn([{application, _Name, Info}]) ->
+    {vsn, Vsn} = lists:keyfind(vsn, 1, Info),
+    Vsn.
+
+make_tar(#{name := Name, rel_vsn := Vsn, rel_apps := Apps} = Info) ->
+    Dir = filename:join(["_build", ?MODULE]),
+    NameWithVsn = binary_to_list(bin([Name, "-", Vsn])),
+    %% write info file
+    InfoFile = filename:join([Dir, NameWithVsn ++ ".json"]),
+    ok = filelib:ensure_dir(InfoFile),
+    ok = file:write_file(InfoFile, jsx:encode(Info, [space, {indent, 4}])),
+    %% copy apps to lib dir
+    LibDir = filename:join([Dir, lib]),
+    ok = rebar_file_utils:rm_rf(LibDir),
+    ok = filelib:ensure_dir(filename:join([LibDir, "foo"])),
+    Sources = lists:map(fun(App) -> filename:join(["_build", "default", "rel", Name, "lib", App]) end, Apps),
+    ok = rebar_file_utils:cp_r(Sources, LibDir),
+    {ok, OriginalCwd} = file:get_cwd(),
+    ok = file:set_cwd(Dir),
+    try
+        do_make_tar(Dir, NameWithVsn)
+    after
+        file:set_cwd(OriginalCwd)
+    end,
+    ok = file:delete(InfoFile).
+
+do_make_tar(Cwd, NameWithVsn) ->
+    Files = filelib:wildcard("lib/**"),
+    TarFile = NameWithVsn ++ ".tar.gz",
+    FullName = filename:join([Cwd, TarFile]),
+    ?LOG(info, "creating ~s", [FullName]),
+    ok = erl_tar:create(TarFile, [NameWithVsn ++ ".json"| Files], [compressed]),
+    {ok, Bin} = file:read_file(TarFile),
+    Sha = bin2hexstr(crypto:hash(sha256, Bin)),
+    ok = file:write_file(NameWithVsn ++ ".sha256", Sha).
+
+bin(X) -> iolist_to_binary(X).
+
+str_list(L) -> lists:map(fun bin/1, L).
+
+info_field(authors, Authors) -> str_list(Authors);
+info_field(builder, Builder) -> info_map(Builder);
+info_field(functionality, Fs) -> str_list(Fs);
+info_field(compatibility, Cs) -> info_map(Cs);
+info_field(_, Value) -> bin(Value).
+
+info_map(InfoList) ->
+    maps:from_list(lists:map(fun({K, V}) -> {K, info_field(K, V)} end, InfoList)).
+
+bin2hexstr(B) when is_binary(B) ->
+    << <<(int2hexchar(H)), (int2hexchar(L))>> || <<H:4, L:4>> <= B>>.
+
+int2hexchar(I) when I >= 0 andalso I < 10 -> I + $0;
+int2hexchar(I) -> I - 10 + $a.