Skip to content

Commit

Permalink
Introduce JSON output (requires OTP 27) and make it default
Browse files Browse the repository at this point in the history
  • Loading branch information
robertoaloi committed Aug 8, 2024
1 parent 053b12e commit 8201256
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 15 deletions.
49 changes: 38 additions & 11 deletions apps/rebar/src/rebar_prv_manifest.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@

-export([init/1,
do/1,
format_error/1]).
format_error/1,
is_json_available/0]).

-include_lib("providers/include/providers.hrl").

-define(PROVIDER, manifest).
-define(NAMESPACE, experimental).
-define(DEFAULT_FORMAT, erlang).
-define(DEFAULT_FORMAT, json).

-type app_context() :: #{name := binary(),
dir => file:filename_all(),
Expand All @@ -24,13 +25,13 @@
include_dirs := [file:filename_all()],
macros => [macro()],
parse_transforms => [any()]}.
-type macro() :: atom() | {atom(), any()}.
-type macro() :: #{key := atom(), value => any()}.
-type manifest() :: #{ apps := [app_context()],
deps := [app_context()],
otp_lib_dir := file:filename_all(),
source_root := file:filename_all()}.

-type format() :: erlang | eetf.
-type format() :: erlang | eetf | json.

%% ===================================================================
%% Public API
Expand Down Expand Up @@ -73,6 +74,8 @@ do(State) ->
-spec format_error(any()) -> iolist().
format_error({format_not_supported, Format}) ->
io_lib:format("Format '~p' is not supported. Try 'erlang' or 'eetf'.", [Format]);
format_error(no_json_module) ->
io_lib:format("The 'json' module is not available. Either upgrade to OTP 27 or use a different format.", []);
format_error({output_error, To, Error}) ->
io_lib:format("Could not output manifest to ~p (~p)", [To, Error]);
format_error(Reason) ->
Expand All @@ -93,7 +96,7 @@ desc() ->
options() ->
[{format, $f, "format", {atom, ?DEFAULT_FORMAT},
"Format for the manifest. "
"Supported formats are: erlang, eetf (Erlang External Binary Format)"},
"Supported formats are: erlang, eetf (Erlang External Binary Format), json"},
{to, $t, "to", {string, undefined},
"If specified, write the manifest to file"}].

Expand Down Expand Up @@ -130,30 +133,54 @@ adapt_context(App) ->
src_dirs => [to_binary(D) || D <- SrcDirs],
extra_src_dirs => [to_binary(D) || D <- ExtraSrcDirs],
include_dirs => [to_binary(D) || D <- IncludeDirs],
macros => Macros,
macros => [to_macro(M) || M <- Macros],
parse_transforms => ParseTransforms}.

-spec output_manifest(binary(), format(), string() | undefined) -> ok | {error, term()}.
output_manifest(Manifest, Format, undefined) ->
rebar_log:log(info, "Writing manifest to stdout:~n", []),
case Format of
erlang ->
io:fwrite("~ts~n", [Manifest]);
eetf ->
io:fwrite("~s~n", [Manifest])
eetf ->
io:fwrite("~s~n", [Manifest]);
_ ->
io:fwrite("~ts~n", [Manifest])
end;
output_manifest(Manifest, _Format, File) ->
rebar_log:log(info, "Build info written to: ~ts~n", [File]),
file:write_file(File, Manifest).

-spec format(manifest(), format()) -> {ok, binary()} | {error, {format_not_supported, term()}}.
-spec format(manifest(), format()) -> {ok, binary()} | {error, {format_not_supported, term()} | no_json_module}.
format(Manifest, eetf) ->
{ok, term_to_binary(Manifest)};
format(Manifest, erlang) ->
{ok, unicode:characters_to_binary(io_lib:format("~p.", [Manifest]))};
format(Manifest, json) ->
case is_json_available() of
true ->
Encoded = erlang:apply(json, encode, [Manifest]),
{ok, Encoded};
false ->
{error, no_json_module}
end;
format(_Manifest, Format) ->
{error, {format_not_supported, Format}}.

-spec to_binary(file:filename()) -> file:filename_all().
to_binary(Path) ->
unicode:characters_to_binary(Path).

-spec to_macro(atom() | {atom() | any()}) -> macro().
to_macro({Key, Value}) when is_atom(Key) ->
#{key => Key, value => Value};
to_macro(Key) when is_atom(Key) ->
#{key => Key}.

-spec is_json_available() -> boolean().
is_json_available() ->
% Requires OTP 27
case code:ensure_loaded(json) of
{module, _} ->
true;
{error, _} ->
false
end.
35 changes: 31 additions & 4 deletions apps/rebar/test/rebar_manifest_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
basic_check/1,
write_to_file_erlang/1,
write_to_file_eetf/1,
write_to_file_json/1,
non_supported_format/1]).

-include_lib("common_test/include/ct.hrl").
Expand All @@ -16,6 +17,7 @@
all() -> [basic_check,
write_to_file_erlang,
write_to_file_eetf,
write_to_file_json,
non_supported_format].

init_per_testcase(Case, Config0) ->
Expand All @@ -33,16 +35,23 @@ end_per_testcase(_, Config) ->
Config.

basic_check(Config) ->
rebar_test_utils:run_and_check(Config, [],
[?NAMESPACE, "manifest"],
{ok, []}).
case rebar_prv_manifest:is_json_available() of
true ->
rebar_test_utils:run_and_check(Config, [],
[?NAMESPACE, "manifest"],
{ok, []});
false ->
rebar_test_utils:run_and_check(Config, [],
[?NAMESPACE, "manifest"],
{error, {rebar_prv_manifest, no_json_module}})
end.

write_to_file_erlang(Config) ->
AppName = proplists:get_value(name, Config),
PrivDir = proplists:get_value(priv_dir, Config),
FilePath = filename:join([PrivDir, "manifest"]),
rebar_test_utils:run_and_check(Config, [],
[?NAMESPACE, "manifest", "--to", FilePath],
[?NAMESPACE, "manifest", "--to", FilePath, "--format", "erlang"],
{ok, []}),
{ok, [Manifest]} = file:consult(FilePath),
?assertMatch(#{deps := [], apps := [#{name := AppName}]}, Manifest).
Expand All @@ -58,6 +67,24 @@ write_to_file_eetf(Config) ->
Manifest = binary_to_term(Content),
?assertMatch(#{deps := [], apps := [#{name := AppName}]}, Manifest).

write_to_file_json(Config) ->
AppName = proplists:get_value(name, Config),
PrivDir = proplists:get_value(priv_dir, Config),
FilePath = filename:join([PrivDir, "manifest"]),
case rebar_prv_manifest:is_json_available() of
true ->
rebar_test_utils:run_and_check(Config, [],
[?NAMESPACE, "manifest", "--to", FilePath],
{ok, []}),
{ok, Content} = file:read_file(FilePath),
Manifest = erlang:apply(json, decode, [Content]),
?assertMatch(#{<<"deps">> := [], <<"apps">> := [#{<<"name">> := AppName}]}, Manifest);
false ->
rebar_test_utils:run_and_check(Config, [],
[?NAMESPACE, "manifest", "--to", FilePath, "--format", "json"],
{error, {rebar_prv_manifest, no_json_module}})
end.

non_supported_format(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FilePath = filename:join([PrivDir, "manifest"]),
Expand Down

0 comments on commit 8201256

Please sign in to comment.