Packages
rebar3_gpb_plugin
2.6.0
3.0.1
3.0.0
2.23.8
2.23.7
2.23.6
2.23.5
2.23.4
2.23.3
2.23.2
2.23.1
2.23.0
2.22.7
2.22.6
2.22.5
2.22.4
2.22.3
2.22.2
2.22.1
2.22.0
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.1
2.19.0
2.18.1
2.18.0
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.0
2.14.0
2.13.2
2.13.1
2.13.0
2.12.5
2.12.4
2.12.3
2.12.2
2.12.1
2.12.0
2.11.3
2.11.2
2.11.1
2.11.0
2.10.0
2.9.0
2.8.2
2.8.0
2.7.0
2.6.1
2.6.0
2.5.1
2.5.0
2.4.3
2.4.2
2.4.1
2.4.0
2.3.4
2.3.3
2.3.2
2.3.1
2.3.0
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.1.1
2.1.0
2.0.1
2.0.0
1.12.1
1.12.0
1.11.8
1.11.7
1.11.6
1.11.5
1.11.4
1.11.3
1.11.2
1.11.1
1.11.0
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.2
1.9.1
1.9.0
1.8.2
1.8.1
1.8.0
1.7.3
1.7.2
1.7.1
1.7.0
1.6.1
1.6.0
1.5.2
1.5.1
1.4.0
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.0.1
A rebar3 gpb plugin for compiling .proto files
Current section
Files
Jump to
Current section
Files
src/rebar3_gpb_compiler.erl
-module(rebar3_gpb_compiler).
-export([compile/2,
clean/2]).
-define(DEFAULT_PROTO_DIR, "proto").
-define(DEFAULT_OUT_ERL_DIR, "src").
-define(DEFAULT_OUT_HRL_DIR, "include").
-define(DEFAULT_MODULE_PREFIX, "").
-define(DEFAULT_MODULE_SUFFIX, "").
%% ===================================================================
%% Public API
%% ===================================================================
-spec compile(rebar_app_info:t(),
rebar_state:t()) -> ok.
compile(AppInfo, State) ->
AppDir = rebar_app_info:dir(AppInfo),
DepsDir = rebar_dir:deps_dir(State),
AppOutDir = rebar_app_info:out_dir(AppInfo),
Opts = rebar_app_info:opts(AppInfo),
{ok, GpbOpts0} = dict:find(gpb_opts, Opts),
%% check if non-recursive
Recursive = proplists:get_value(recursive, GpbOpts0, true),
SourceDirs = proplists:get_all_values(i, GpbOpts0),
TargetErlDir = filename:join([AppOutDir,
proplists:get_value(o_erl, GpbOpts0,
?DEFAULT_OUT_ERL_DIR)]),
TargetHrlDir = filename:join([AppOutDir,
proplists:get_value(o_hrl, GpbOpts0,
?DEFAULT_OUT_HRL_DIR)]),
rebar_api:debug("making sure that target erl dir ~p exists", [TargetErlDir]),
ok = ensure_dir(TargetErlDir),
rebar_api:debug("making sure that target hrl dir ~p exists", [TargetHrlDir]),
ok = ensure_dir(TargetHrlDir),
rebar_api:debug("reading proto files from ~p, generating \".erl\" to ~p "
"and \".hrl\" to ~p",
[SourceDirs, TargetErlDir, TargetHrlDir]),
%% search for .proto files
Protos = lists:foldl(fun({deps, SourceDir}, Acc) ->
Acc ++ discover(DepsDir, SourceDir, [{recursive, Recursive}]);
(SourceDir, Acc) ->
Acc ++ discover(AppDir, SourceDir, [{recursive, Recursive}])
end, [], SourceDirs),
rebar_api:debug("proto files found~s: ~p",
[case Recursive of true -> " recursively"; false -> "" end, Protos]),
%% set the full path for the output directories
%% add to include path dir locations of the protos
%% remove the plugin specific options since gpb will not understand them
GpbOpts = remove_plugin_opts(
proto_include_paths(AppDir, Protos,
default_include_opts(AppDir, DepsDir,
target_erl_opt(TargetErlDir,
target_hrl_opt(TargetHrlDir, GpbOpts0))))),
compile(Protos, TargetErlDir, GpbOpts, Protos),
ok.
-spec clean(rebar_app_info:t(),
rebar_state:t()) -> ok.
clean(AppInfo, State) ->
AppDir = rebar_app_info:dir(AppInfo),
DepsDir = rebar_dir:deps_dir(State),
AppOutDir = rebar_app_info:out_dir(AppInfo),
Opts = rebar_app_info:opts(AppInfo),
{ok, GpbOpts} = dict:find(gpb_opts, Opts),
ModuleNamePrefix = proplists:get_value(module_name_prefix, GpbOpts,
?DEFAULT_MODULE_PREFIX),
ModuleNameSuffix = proplists:get_value(module_name_suffix, GpbOpts,
?DEFAULT_MODULE_SUFFIX),
TargetErlDir = filename:join([AppOutDir,
proplists:get_value(o_erl, GpbOpts,
?DEFAULT_OUT_ERL_DIR)]),
TargetHrlDir = filename:join([AppOutDir,
proplists:get_value(o_hrl, GpbOpts,
?DEFAULT_OUT_HRL_DIR)]),
ProtoFiles = find_proto_files(AppDir, DepsDir, GpbOpts),
rebar_api:debug("found proto files: ~p", [ProtoFiles]),
GeneratedRootFiles = [ModuleNamePrefix ++
filename:rootname(filename:basename(ProtoFile)) ++
ModuleNameSuffix || ProtoFile <- ProtoFiles],
GeneratedErlFiles = [filename:join([TargetErlDir, F ++ ".erl"]) ||
F <- GeneratedRootFiles],
GeneratedHrlFiles = [filename:join([TargetHrlDir, F ++ ".hrl"]) ||
F <- GeneratedRootFiles],
rebar_api:debug("deleting [~p, ~p]",
[GeneratedErlFiles, GeneratedHrlFiles]),
rebar_file_utils:delete_each(GeneratedErlFiles ++ GeneratedHrlFiles).
%% ===================================================================
%% Private API
%% ===================================================================
discover(AppDir, SourceDir, Opts) ->
%% Convert simple extension to proper regex
SourceExtRe = "^[^._].*\\" ++ ".proto" ++ [$$],
Recursive = proplists:get_value(recursive, Opts, true),
%% Find all possible source files
rebar_utils:find_files(filename:join([AppDir, SourceDir]),
SourceExtRe, Recursive).
compile([], _TargetErlDir, _GpbOpts, _Protos) -> ok;
compile([Proto | Rest], TargetErlDir, GpbOpts, Protos) ->
ModuleNamePrefix = proplists:get_value(module_name_prefix, GpbOpts,
?DEFAULT_MODULE_PREFIX),
ModuleNameSuffix = proplists:get_value(module_name_suffix, GpbOpts,
?DEFAULT_MODULE_SUFFIX),
Target = target_file(Proto, ModuleNamePrefix, ModuleNameSuffix, TargetErlDir),
Deps =
case filelib:last_modified(Target) < filelib:last_modified(Proto) of
true ->
ok = compile(Proto, Target, GpbOpts),
%% now we know that thid proto needed compilation we check
%% for other protos that might have included this one and ensure that
%% those are compiled as well
Deps0 = get_dependencies(Proto, Protos, GpbOpts),
rebar_api:debug("protos that include ~p: ~p",
[Proto, Deps0]),
%% now touch the targets of each of the deps so they get remade again
lists:foreach(fun(Dep) ->
DepTarget = target_file(Dep, ModuleNamePrefix, ModuleNameSuffix,
TargetErlDir),
%% we want to force compilation in this case so we must trick
%% the plugin into believing that the proto is more recent than the
%% target, this means we need to set the last changed time on the
%% target to a time earlier than the proto
Seconds = calendar:datetime_to_gregorian_seconds(
filelib:last_modified(Dep)) - 60,
_ = file:change_time(DepTarget,
calendar:gregorian_seconds_to_datetime(Seconds)),
rebar_api:debug("touched ~p", [DepTarget])
end, Deps0),
Deps0;
false ->
[]
end,
compile(Rest ++ Deps, TargetErlDir, GpbOpts, Protos).
get_dependencies(Proto, Protos, GpbOpts) ->
%% go through each of the protos and for each one
%% check if it included the provided proto, return
%% the ones tht do
lists:filtermap(fun(Proto0) ->
filter_included_proto(Proto0, Proto, GpbOpts)
end, Protos).
filter_included_proto(Source, Dep, GpbOpts) ->
{ok, Defs, _Warnings} = gpb_compile:file(filename:basename(Source),
GpbOpts ++ [to_proto_defs, return]),
Imports = lists:map(fun(Import0) ->
{ok, Import} = gpb_compile:locate_import(Import0, GpbOpts),
filename:absname(Import)
end, lists:usort(gpb_parse:fetch_imports(Defs))),
case lists:member(Dep, Imports) of
true -> {true, Source};
false -> false
end.
target_file(Proto, ModuleNamePrefix, ModuleNameSuffix, TargetErlDir) ->
Module = filename:basename(Proto, ".proto"),
filename:join([TargetErlDir, ModuleNamePrefix ++ Module ++ ModuleNameSuffix ++ ".erl"]).
-spec compile(string(), string(), proplists:proplist()) -> ok.
compile(Source, Target, GpbOpts) ->
rebar_api:debug("compiling ~p to ~p", [Source, Target]),
rebar_api:debug("opts: ~p", [GpbOpts]),
case gpb_compile:file(filename:basename(Source), GpbOpts) of
ok ->
ok;
{ok, []} ->
ok;
{error, Reason} ->
ReasonStr = gpb_compile:format_error(Reason),
rebar_utils:abort("failed to compile ~s: ~s~n", [Source, ReasonStr])
end.
-spec ensure_dir(filelib:dirname()) -> 'ok' | {error, Reason::file:posix()}.
ensure_dir(OutDir) ->
%% Make sure that ebin/ exists and is on the path
case filelib:ensure_dir(filename:join(OutDir, "dummy.beam")) of
ok -> ok;
{error, eexist} ->
rebar_utils:abort("unable to ensure dir ~p, is it maybe a broken symlink?",
[OutDir]);
{error, Reason} -> {error, Reason}
end.
-spec default_include_opts(string(), string(), proplists:proplist()) -> proplists:proplist().
default_include_opts(AppDir, DepsDir, Opts) ->
lists:map(fun({i, {deps, Path}}) ->
{i, filename:join(DepsDir, Path)};
({i, Path}) ->
{i, filename:join(AppDir, Path)};
({ipath, {deps, Path}}) ->
{i, filename:join(DepsDir, Path)};
({ipath, Path}) ->
{i, filename:join(AppDir, Path)};
(Opt) -> Opt
end, Opts).
-spec target_erl_opt(string(), proplists:proplist()) -> proplists:proplist().
target_erl_opt(Dir, Opts) ->
lists:keystore(o_erl, 1, Opts, {o_erl, Dir}).
-spec target_hrl_opt(string(), proplists:proplist()) -> proplists:proplist().
target_hrl_opt(Dir, Opts) ->
lists:keystore(o_hrl, 1, Opts, {o_hrl, Dir}).
-spec remove_plugin_opts(proplists:proplists()) -> proplists:proplist().
remove_plugin_opts(Opts) ->
remove_plugin_opts(Opts, [recursive, ipath]).
-spec remove_plugin_opts(proplists:proplist(),
[ipath | recursive]) -> proplists:proplist().
remove_plugin_opts(Opts, []) -> Opts;
remove_plugin_opts(Opts0, [OptToRemove | Rest]) ->
Opts = lists:keydelete(OptToRemove, 1, Opts0),
remove_plugin_opts(Opts, Rest).
find_proto_files(AppDir, DepsDir, GpbOpts) ->
lists:foldl(fun({deps, SourceDir}, Acc) ->
Acc ++ rebar_utils:find_files(
filename:join(DepsDir, SourceDir),
".*\.proto\$");
(SourceDir, Acc) ->
Acc ++ rebar_utils:find_files(
filename:join(AppDir, SourceDir),
".*\.proto\$")
end,
[], proplists:get_all_values(i, GpbOpts)).
proto_include_paths(_AppDir, [], Opts) -> Opts;
proto_include_paths(AppDir, [Proto | Protos], Opts) ->
ProtoDir = filename:join([AppDir, filename:dirname(Proto)]),
proto_include_paths(AppDir, Protos, Opts ++ [{i, ProtoDir}]).