Packages
rebar3_hex
2.1.0
7.1.0
7.0.11
7.0.10
7.0.9
7.0.8
7.0.7
7.0.6
7.0.5
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.11.9
6.11.8
6.11.7
6.11.6
6.11.5
6.11.4
6.11.3
6.11.2
6.11.1
6.11.0
6.10.3
6.10.2
6.10.1
6.10.0
6.9.6
6.9.5
6.9.4
6.9.3
6.9.2
6.9.1
6.9.0
6.8.0
6.7.0
6.6.0
6.5.0
6.4.0
6.3.0
6.2.0
6.1.0
6.0.0
4.1.0
4.0.0
3.1.0
3.0.0
2.5.1
2.5.0
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
1.20.0
1.19.0
1.18.0
1.17.0
1.16.0
1.15.0
1.14.0
1.13.0
1.12.0
1.11.0
1.10.0
1.9.1
1.9.0
1.8.1
1.8.0
1.7.2
1.7.1
1.7.0
1.6.3
1.6.1
1.6.0
1.5.0
1.4.0
1.3.0
1.2.0
1.1.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Hex.pm plugin for rebar3
Current section
Files
Jump to
Current section
Files
src/rebar3_hex_pkg.erl
-module(rebar3_hex_pkg).
-export([init/1,
do/1,
format_error/1]).
-export([publish/2
,publish/6
,validate_app_details/1]).
-include("rebar3_hex.hrl").
-define(PROVIDER, publish).
-define(DEPS, [{default, lock}]).
-define(ENDPOINT, "packages").
%% ===================================================================
%% Public API
%% ===================================================================
-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
Provider = providers:create([
{name, ?PROVIDER},
{module, ?MODULE},
{namespace, hex},
{bare, true},
{deps, ?DEPS},
{example, "rebar3 hex publish"},
{short_desc, "Publish a new version of your package and update the package"},
{desc, ""},
{opts, [{revert, undefined, "revert", string, "Revert given version."}]}
]),
State1 = rebar_state:add_provider(State, Provider),
{ok, State1}.
-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}.
do(State) ->
Apps = rebar3_hex_utils:select_apps(rebar_state:project_apps(State)),
lists:foldl(fun(App, {ok, StateAcc}) ->
do_(App, StateAcc)
end, {ok, State}, Apps).
-spec format_error(any()) -> iolist().
format_error({non_hex_deps, Excluded}) ->
io_lib:format("Can not publish package because the following deps are not available in hex: ~s", [string:join(Excluded, ", ")]);
format_error(has_contributors) ->
"The contributors field is deprecated, please change to maintainers and rerun.";
format_error(undefined_server_error) ->
"Unknown server error";
format_error({status, Status}) ->
rebar3_hex_http:pretty_print_status(Status);
format_error({status, Status, undefined_server_error}) ->
"Unknown server error: " ++ rebar3_hex_http:pretty_print_status(Status);
format_error({status, Status, Error}) ->
Message = maps:get(<<"message">>, Error, ""),
Errors = maps:get(<<"errors">>, Error, ""),
ErrorString = errors_to_string(Errors),
io_lib:format("Status Code: ~s~nHex Error: ~s~n\t~s", [rebar3_hex_http:pretty_print_status(Status),
Message, ErrorString]).
%% ===================================================================
%% Public API
%% ===================================================================
do_(App, State) ->
Name = rebar_app_info:name(App),
{Args, _} = rebar_state:command_parsed_args(State),
Revert = proplists:get_value(revert, Args, undefined),
case Revert of
undefined ->
case publish(App, State) of
ok ->
{ok, State};
stopped ->
{ok, State};
Error ->
Error
end;
Version ->
case delete(App, Name, Version) of
ok ->
{ok, State};
Error ->
Error
end
end.
publish(App, State) ->
AppDir = rebar_app_info:dir(App),
Name = rebar_app_info:name(App),
Version = rebar_app_info:original_vsn(App),
ResolvedVersion = rebar_utils:vcs_vsn(Version,
rebar_app_info:dir(App),
rebar_state:resources(State)),
{application, _, AppDetails} = rebar3_hex_utils:update_app_src(App, ResolvedVersion),
Deps = rebar_state:get(State, {locks, default}, []),
TopLevel = [{N, [{<<"app">>, N}, {<<"optional">>, false}, {<<"requirement">>, V}]} || {_,{pkg,N,V},0} <- Deps],
Excluded = [binary_to_list(N) || {N,{T,_,_},0} <- Deps, T =/= pkg],
case validate_app_details(AppDetails) of
ok ->
publish(AppDir, Name, ResolvedVersion, TopLevel, Excluded, AppDetails);
Error ->
Error
end.
publish(AppDir, Name, Version, Deps, [], AppDetails) ->
Config = rebar_config:consult(AppDir),
ConfigDeps = proplists:get_value(deps, Config, []),
Deps1 = update_versions(ConfigDeps, Deps),
Description = list_to_binary(proplists:get_value(description, AppDetails, "")),
FilePaths = proplists:get_value(files, AppDetails, ?DEFAULT_FILES),
AppSrc = {application, ec_cnv:to_atom(Name), AppDetails},
Files = rebar3_hex_utils:expand_paths(FilePaths, AppDir),
AppFileSrc = filename:join("src", ec_cnv:to_list(Name)++".app.src"),
AppSrcBinary = ec_cnv:to_binary(lists:flatten(io_lib:format("~tp.\n", [AppSrc]))),
Files1 = [{AppFileSrc, AppSrcBinary} | lists:keydelete(AppFileSrc, 1, Files)],
MetaDataFiles = [File || {File, _} <- Files],
Maintainers = proplists:get_value(maintainers, AppDetails, []),
Licenses = proplists:get_value(licenses, AppDetails, []),
Links = proplists:get_value(links, AppDetails, []),
%% We check the app file for the 'pkg' key wich allows us to select
%% a package name other then the app anem, if it is not set we default
%% back to the app name.
PkgName = ec_cnv:to_binary(proplists:get_value(pkg_name, AppDetails, Name)),
Optional = [{app, Name}
,{requirements, Deps1}
,{maintainers, Maintainers}
,{precompiled, false}
,{parameters, []}
,{description, Description}
,{files, MetaDataFiles}
,{licenses, Licenses}
,{links, Links}
,{build_tools, [<<"rebar3">>]}],
OptionalFiltered = [{Key, Value} || {Key, Value} <- Optional, Value =/= []],
Meta = [{name, PkgName}, {version, Version} | OptionalFiltered],
Filenames = [F || {_, F} <- Files],
{ok, Auth} = rebar3_hex_config:auth(),
ec_talk:say("Publishing ~s ~s", [PkgName, Version]),
ec_talk:say(" Dependencies:~n ~s", [format_deps(Deps1)]),
ec_talk:say(" Included files:~n ~s", [string:join(Filenames, "\n ")]),
ec_talk:say("Before publishing, please read Hex CoC: https://hex.pm/docs/codeofconduct", []),
case ec_talk:ask_default("Proceed?", boolean, "Y") of
true ->
upload_package(Auth, PkgName, Version, Meta, Files1);
_ ->
ec_talk:say("Goodbye..."),
stopped
end;
publish(_AppDir, _Name, _Version, _Deps, Excluded, _AppDetails) ->
?PRV_ERROR({non_hex_deps, Excluded}).
%% Internal functions
validate_app_details(AppDetails) ->
case proplists:is_defined(contributors, AppDetails) of
true ->
{error, {rebar3_hex_pkg, has_contributors}};
false ->
ok
end.
delete(App, Name, Version) ->
{ok, Auth} = rebar3_hex_config:auth(),
{application, _, AppDetails} = rebar3_hex_utils:update_app_src(App, Version),
PkgName = ec_cnv:to_binary(proplists:get_value(pkg_name, AppDetails, Name)),
case rebar3_hex_http:delete(filename:join([?ENDPOINT, PkgName, "releases", Version]), Auth) of
ok ->
rebar_api:info("Successfully deleted package ~s ~s", [PkgName, Version]),
case ec_talk:ask_default(io_lib:format("Also delete tag v~s?", [Version]), boolean, "N") of
true ->
rebar_utils:sh(io_lib:format("git tag -d v~s", [Version]), []);
_ ->
ok
end;
{error, Status} ->
rebar_api:error("Unable to delete package ~s ~s (~p)", [PkgName, Version, Status])
end.
upload_package(Auth, Name, Version, Meta, Files) ->
{ok, Tar} = rebar3_hex_tar:create(Name, Version, Meta, Files),
case rebar3_hex_http:post(filename:join([?ENDPOINT, Name, "releases"])
,Auth
,Tar
,integer_to_list(byte_size(Tar))) of
ok ->
rebar_api:info("Published ~s ~s", [Name, Version]),
ok;
{error, Status, <<>>} ->
?PRV_ERROR({status, Status});
{error, Status, Error} ->
?PRV_ERROR({status, Status, Error})
end.
errors_to_string(Value) when is_binary(Value) ->
Value;
errors_to_string({Key, Value}) ->
io_lib:format("~s: ~s", [Key, errors_to_string(Value)]);
errors_to_string(Errors) when is_list(Errors) ->
lists:flatten([io_lib:format("~s", [errors_to_string(Values)]) || Values <- Errors]).
format_deps(Deps) ->
string:join([binary_to_list(<<N/binary, " ", V/binary>>) || {N, [{_, N}, {_, _}, {<<"requirement">>, V}]} <- Deps], "\n ").
update_versions(ConfigDeps, Deps) ->
[begin
case lists:keyfind(binary_to_atom(N, utf8), 1, ConfigDeps) of
{_, V} ->
{N, lists:keyreplace(<<"requirement">>, 1, M, {<<"requirement">>, list_to_binary(V)})};
_ ->
{N, M}
end
end || {N, M} <- Deps].