diff --git a/ebin/rebar.app b/ebin/rebar.app index 55454e9..f92f574 100644 --- a/ebin/rebar.app +++ b/ebin/rebar.app @@ -28,6 +28,7 @@ rebar_neotoma_compiler, rebar_otp_app, rebar_port_compiler, + rebar_proto_compiler, rebar_protobuffs_compiler, rebar_qc, rebar_rel_utils, @@ -67,6 +68,7 @@ {modules, [ {app_dir, [ rebar_abnfc_compiler, + rebar_proto_compiler, rebar_protobuffs_compiler, rebar_neotoma_compiler, rebar_asn1_compiler, diff --git a/rebar.config.sample b/rebar.config.sample index d154ac1..d2a7869 100644 --- a/rebar.config.sample +++ b/rebar.config.sample @@ -89,6 +89,12 @@ %% Options for the ErlyDTL compiler {erlydtl_opts, []}. +%% == Proto compiler == +{proto_compiler, protobuffs}. +%% Available compilers for protocol buffer files (*.proto): +%% protobuffs (default) + + %% == EUnit == %% Options for eunit:test() diff --git a/src/rebar_proto_compiler.erl b/src/rebar_proto_compiler.erl new file mode 100644 index 0000000..c86dda2 --- /dev/null +++ b/src/rebar_proto_compiler.erl @@ -0,0 +1,144 @@ +%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% rebar: Erlang Build Tools +%% +%% Copyright (c) 2014 Tomas Abrahamsson (tomas.abrahamsson@gmail.com) +%% +%% Permission is hereby granted, free of charge, to any person obtaining a copy +%% of this software and associated documentation files (the "Software"), to deal +%% in the Software without restriction, including without limitation the rights +%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%% copies of the Software, and to permit persons to whom the Software is +%% furnished to do so, subject to the following conditions: +%% +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +%% THE SOFTWARE. +%% ------------------------------------------------------------------- +-module(rebar_proto_compiler). + +-export([compile/2, + clean/2]). + +%% for internal use only +-export([info/2]). + +-include("rebar.hrl"). + +-record(proto_compiler, + {key :: atom(), % Corresponds to the {proto_compiler,Key} + module :: atom() + }). + +%% =================================================================== +%% Public API +%% =================================================================== + +compile(Config, AppFile) -> + case rebar_utils:find_files("src", "^[^._].*\\.proto$") of + [] -> + ok; + Protos -> + %% Find a compiler for protocol buffers, + %% use that for compiling protocol buffers + CompilerModule = select_proto_compiler(Config), + CompilerModule:proto_compile(Config, AppFile, Protos) + end. + +clean(Config, AppFile) -> + %% Get a list of generated .beam and .hrl files and then delete them + Protos = rebar_utils:find_files("src", "^[^._].*\\.proto$"), + case Protos of + [] -> + ok; + _ -> + %% Ask the proto compiler to compile the .proto files. + CompilerModule = select_proto_compiler(Config), + CompilerModule:proto_clean(Config, AppFile, Protos) + end. + +%% =================================================================== +%% Internal functions +%% =================================================================== + +info(help, compile) -> + info_help("Build protocol buffer (*.proto) sources", compile); +info(help, clean) -> + info_help("Delete protocol buffer (*.proto) build results", clean). + +info_help(GeneralDescr, Cmd) -> + ?CONSOLE( + "~s.~n" + ++ "~n" + ++ "Valid rebar.config options:~n" + ++ " {proto_compiler, Compiler}~n" + ++ "The following protocol buffer compilers are available:~n" + ++ "~s~n", + [GeneralDescr, format_proto_compiler_list()]), + %% Print info for each proto compiler + [begin + ?CONSOLE("--- ~p ---~n", [Key]), + CompilerModule:proto_info(help, Cmd) + end + || #proto_compiler{key=Key, + module=CompilerModule} <- find_proto_compilers()], + ok. + +get_default_compiler() -> + protobuffs. + +find_proto_compilers() -> + {ok, RebarModuleGroups} = application:get_env(rebar, modules), + {app_dir, Modules} = lists:keyfind(app_dir, 1, RebarModuleGroups), + [#proto_compiler{key = M:key(), + module = M} + || M <- Modules, + is_proto_compiler_module(M)]. + +is_proto_compiler_module(Module) -> + case code:ensure_loaded(Module) of + {module, Module} -> + lists:all(fun({Function, Arity}) -> + erlang:function_exported(Module, Function, Arity) + end, + [{key, 0}, + {proto_compile, 3}, + {proto_clean, 3}, + {proto_info, 2}]); + _ -> + false + end. + +select_proto_compiler(Config) -> + Default = get_default_compiler(), + Key = rebar_config:get_local(Config, proto_compiler, Default), + AvailCompilers = find_proto_compilers(), + case lists:keyfind(Key, #proto_compiler.key, AvailCompilers) of + #proto_compiler{module=CompilerModule} -> + CompilerModule; + false -> + ?ABORT("No such protocol buffer compiler known, '~s'~n" + ++ "The following are known:~n" + ++ "~s~n", + [Key, format_proto_compiler_list()]) + end. + +format_proto_compiler_list() -> + Default = get_default_compiler(), + Keys = [Key || #proto_compiler{key=Key} <- find_proto_compilers()], + Annotations = [if Key == Default -> " (default)"; + true -> "" + end + || Key <- Keys], + lists:flatten( + [?FMT(" ~p~s~n", [Key, Annotation]) + || {Key, Annotation} <- lists:zip(Keys, Annotations)]). diff --git a/src/rebar_protobuffs_compiler.erl b/src/rebar_protobuffs_compiler.erl index e89c700..2b0b621 100644 --- a/src/rebar_protobuffs_compiler.erl +++ b/src/rebar_protobuffs_compiler.erl @@ -26,11 +26,12 @@ %% ------------------------------------------------------------------- -module(rebar_protobuffs_compiler). --export([compile/2, - clean/2]). +-export([key/0, + proto_compile/3, + proto_clean/3]). %% for internal use only --export([info/2]). +-export([proto_info/2]). -include("rebar.hrl"). @@ -38,58 +39,48 @@ %% Public API %% =================================================================== -compile(Config, _AppFile) -> - case rebar_utils:find_files("src", "^[^._].*\\.proto$") of - [] -> - ok; - FoundFiles -> - %% Check for protobuffs library -- if it's not present, fail - %% since we have.proto files that need building - case protobuffs_is_present() of - true -> - %% Build a list of output files - { Proto, Beam, Hrl } - Targets = [{Proto, beam_file(Proto), hrl_file(Proto)} || - Proto <- FoundFiles], +key() -> + protobuffs. - %% Compile each proto file - compile_each(Config, Targets); - false -> - ?ERROR("Protobuffs library not present in code path!\n", - []), - ?FAIL - end +proto_compile(Config, _AppFile, ProtoFiles) -> + %% Check for protobuffs library -- if it's not present, fail + %% since we have.proto files that need building + case protobuffs_is_present() of + true -> + %% Build a list of output files - { Proto, Beam, Hrl } + Targets = [{Proto, beam_file(Proto), hrl_file(Proto)} || + Proto <- ProtoFiles], + + %% Compile each proto file + compile_each(Config, Targets); + false -> + ?ERROR("Protobuffs library not present in code path!\n", + []), + ?FAIL end. -clean(_Config, _AppFile) -> +proto_clean(_Config, _AppFile, ProtoFiles) -> %% Get a list of generated .beam and .hrl files and then delete them - Protos = rebar_utils:find_files("src", ".*\\.proto$"), - BeamFiles = [fq_beam_file(F) || F <- Protos], - HrlFiles = [fq_hrl_file(F) || F <- Protos], + BeamFiles = [fq_beam_file(F) || F <- ProtoFiles], + HrlFiles = [fq_hrl_file(F) || F <- ProtoFiles], Targets = BeamFiles ++ HrlFiles, - case Targets of - [] -> - ok; - _ -> - delete_each(Targets) - end. + delete_each(Targets). %% =================================================================== %% Internal functions %% =================================================================== -info(help, compile) -> - info_help("Build Protobuffs (*.proto) sources"); -info(help, clean) -> - info_help("Delete Protobuffs (*.proto) build results"). +proto_info(help, compile) -> + info_help(); +proto_info(help, clean) -> + info_help(). -info_help(Description) -> +info_help() -> ?CONSOLE( - "~s.~n" - "~n" "Valid rebar.config options:~n" " erl_opts is passed as compile_flags to " "protobuffs_compile:scan_file/2~n", - [Description]). + []). protobuffs_is_present() -> code:which(protobuffs_compile) =/= non_existing.