%%% Copyright (C) 2010-2013 Tomas Abrahamsson %%% %%% Author: Tomas Abrahamsson %%% %%% This library is free software; you can redistribute it and/or %%% modify it under the terms of the GNU Lesser General Public %%% License as published by the Free Software Foundation; either %%% version 2.1 of the License, or (at your option) any later version. %%% %%% This library is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %%% Lesser General Public License for more details. %%% %%% You should have received a copy of the GNU Lesser General Public %%% License along with this library; if not, write to the Free Software %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, %%% MA 02110-1301 USA -module(gpb_compile). %-compile(export_all). -export([file/1, file/2]). -export([proto_defs/2, proto_defs/3]). -export([msg_defs/2, msg_defs/3]). -export([format_error/1, format_warning/1]). -export([c/0, c/1]). % Command line interface, halts vm---don't use from shell! -export([parse_opts/2, opt_specs/0, find_opt_spec/1, show_args/0, show_version/0]). -include_lib("kernel/include/file.hrl"). -include_lib("eunit/include/eunit.hrl"). -include("../include/gpb.hrl"). -include("gpb_codegen.hrl"). -record(ft, {type, occurrence, is_packed}). -record(anres, %% result of analysis { used_types, % :: sets:set(gpb_field_type()), known_msg_size, % :: dict:dict(), %% MsgName -> Size | undefined msg_occurrences, % :: dict:dict(), %% MsgName -> [occurrence()] fixlen_types, % :: sets:set(#ft{}), num_packed_fields, % :: integer(), num_fields, % :: dict:dict(), %% MsgName -> integer() d_field_pass_method % :: dict:dict() %% MsgName -> pass_as_record | % %% pass_as_params }). -define(f(Fmt), io_lib:format(Fmt, [])). -define(f(Fmt, Args), io_lib:format(Fmt, Args)). -define(ff(Fmt, Args), lists:flatten(io_lib:format(Fmt, Args))). %% Varints are processed 7 bits at a time. %% We can expect that we have processed this number of bits before %% we expect to see the last varint byte, which must have msb==0. %% 64 - 7 = 57. -define(NB, 57). %% @spec file(File) -> ok | {error, Reason} %% @equiv file(File, []) file(File) -> file(File, []). %% @spec file(File, Opts) -> CompRet %% File = string() %% Opts = [Opt] %% Opt = {type_specs, boolean()} | type_specs | %% {verify, optionally | always | never} | %% {copy_bytes, true | false | auto | integer() | float()} | %% {strings_as_binaries, boolean()} | strings_as_binaries | %% {defs_as_proplists, boolean()} | defs_as_proplists | %% {descriptor,boolean()} | descriptor | %% {maps,boolean()} | maps | %% {nif,boolean()} | nif | %% {load_nif, LoadNif} | %% {i, directory()} | %% {o, directory()} | %% {o_erl, directory()} | {o_hrl, directory()} | %% {o_nif_cc, directory()} | %% binary | to_proto_defs | to_msg_defs | %% return | return_warnings | return_errors | %% report | report_warnings | report_errors | %% include_as_lib | use_packages | %% {msg_name_prefix, string() | atom()} | %% {msg_name_suffix, string() | atom()} | %% {module_name_prefix, string() | atom()} | %% {module_name_suffix, string() | atom()} %% CompRet = ModRet | BinRet | ErrRet %% ModRet = ok | {ok, Warnings} %% BinRet = {ok, ModuleName, Code} | %% {ok, ModuleName, Code, Warnings} %% ErrRet = {error, Reason} | {error,Reason,Warnings} %% ModuleName = atom() %% Code = binary() | ErlAndNifCode %% ErlAndNifCode = [CodeType] %% CodeType = {erl, binary()} | {nif, NifCcText} %% NifCcText = binary() %% LoadNif = string() %% %% @doc %% Compile a .proto file to a .erl file and to a .hrl file. %% %% The File must not include path to the .proto file. Example: %% "SomeDefinitions.proto" is ok, while "/path/to/SomeDefinitions.proto" %% is not ok. %% %% The .proto file is expected to be found in a directories specified by an %% `{i,directory()}' option. It is possible to specify `{i,directory()}' %% several times, they will be searched in the order specified. %% %% The `{type_specs,boolean()}' option enables or disables `::Type()' %% annotations in the generated .hrl file. Default is currently %% `false'. If you set it to `true', you may get into troubles for %% messages referencing other messages, when compiling the generated %% files. The `type_specs' option is equivalent to `{type_specs,true}'. %% %% The `verify' option specifies whether or not to generate code %% that verifies, during encoding, that values are of correct type and %% within range. The `verify' option can have the following values: %%
%%
`always'
Generate code that unconditionally %% verifies values.
%%
`never'
Generate code that never verifies %% values time. Encoding will fail if a value of the wrong %% type is supplied. This includes forgetting to set a required %% message field. Encoding may silently truncate values out of %% range for some types.
%%
`optionally'
Generate an `encode_msg/2' that accepts %% the run-time option `verify' or `{verify,boolean()}' for specifying %% whether or not to verify values.
%%
%% %% Erlang value verfication either succeeds or crashes with the `error' %% `{gpb_type_error,Reason}'. Regardless of the `verify' option, %% a function, `verify_msg/1' is always generated. %% %% The `copy_bytes' option specifies whether when decoding data of %% type `bytes' (or strings if the `strings_as_binaries' is set), the %% decoded bytes should be copied or not. Copying requires the %% `binary' module, which first appeared in Erlang R14A. When not %% copying decoded bytes, they will become sub binaries of the larger %% input message binary. This may tie up the memory in the input %% message binary longer than necessary after it has been %% decoded. Copying the decoded bytes will avoid creating sub %% binaries, which will in turn make it possible to free the input message %% binary earlier. The `copy_bytes' option can have the following values: %%
%%
`false'
Never copy bytes/(sub-)binaries.
%%
`true'
Always copy bytes/(sub-)binaries.
%%
`auto'
Copy bytes/(sub-)binaries if the beam vm, %% on which the compiler (this module) is running, %% has the `binary:copy/1' function. (This is the default)
%%
integer() | float()
Copy the bytes/(sub-)binaries if the %% message this many times or more larger than the size of the %% bytes/(sub-)binary.
%%
%% %% The `strings_as_binaries' option specifies whether strings should %% be returned from decoding as strings (list of unicode code points), %% or as binaries (UTF-8 encoded). The `copy_bytes' option applies %% to strings as well, when the `strings_as_binaries' option is set. %% Upon encoding, both binaries and lists are accepted. %% %% The `defs_as_proplists' option changes the generated introspection %% functions `find_msg_def' and `get_msg_defs' to return the description %% of each message field as a proplist, instead of as a `#field{}' record. %% The purpose is to make the generated code completely independent %% of gpb, at compile-time (it is already independent at run-time). %% The keys of the proplist are the names of the record fields in the %% `#field{}' record. See also {@link gpb:proplists_to_field_records()} %% and related functions for conversion functions between these two %% formats. %% %% The `descriptor' option specifies whether or not to generate a %% function, descriptor/0, which returns a binary that describes the %% proto file(s) contents according to the protobuf's `descriptor.proto'. %% The default is to not generate such a description. The generated %% description binary is most likely not identical to what `protoc' %% would generate, but the contents is roughly equivalent. %% %% The `{o,directory()}' option specifies directory to use for storing %% the generated `.erl' and `.hrl' files. Default is the same %% directory as for the proto `File'. %% %% The `{o_erl,directory()}', `{o_hrl,directory()}', `{o_nif_cc,directory()}', %% options specify output directories for where to generate the `.erl' %% and `.hrl' files respectively, and for the NIF C++ file, %% if the `nif' option is specified. The `{o_erl,directory()}' option %% overrides any `{o,directory()}' option, and similarly for the %% other file-type specific output options. %% %% The `maps' option will generate a protobuf encoder/decoder that %% uses maps instead of records. It will not generate any `.hrl' file, %% and the functions `encode_msg', `merge_msgs' and `verify_msg' will %% take the message name as an additional parameter. The introspection %% will generate message field descriptions as maps instead of as %% `#field{}' records, unless, of course `defs_as_proplists' is specified, %% in which case they will be proplists instead. This option is not %% compatible with the `nif' option. %% %% The `nif' option will cause the compiler to generate the decoder as %% nif C++ code. The generated nif C++ code can be linked with the %% Google protobuf C++ library. Read the file `README.nif-cc' for %% more info. This option is not compatible with the `maps' option; %% the generated C++ decoding code would still create records. %% %% The `binary' option will cause the generated and compiled code to be %% returned as a binary. No files will be written. The return value %% will be on the form `{ok,Mod,Code}' or `{ok,Mod,Code,Warnings}' %% if the compilation is succesful. This option may be useful %% e.g. when generating test cases. In case the `nif' option is set, %% the `Code' will be a list of tuples: `{erl,binary()}' which %% contains the erlang object byte code, and `{nif,binary()}' which %% contains the C++ code. You will have to compile the C++ code with a %% C++ compiler, before you can use the erlang code. %% %% The `to_proto_defs' option will result in `{ok,Defs}' or %% `{ok,Defs,Warns}' being returned if the compilation is succesful. %% The returned message definitions can be used with the %% {@link proto_defs/2} or {@link proto_defs/3} functions. %% %% The `to_msg_defs' option is a deprecated alias for `to_proto_defs'. %% %%
%%
`report_errors'/`report_warnings'
%%
Causes errors/warnings to be printed as they occur.
%%
`report'
%%
This is a short form for both `report_errors' and %% `report_warnings'.
%%
`return_errors'
%%
If this flag is set, then `{error,ErrorList,WarningList}' is %% returned when there are errors.
%%
`return_warnings'
%%
If this flag is set, then an extra field containing `WarningList' %% is added to the tuples returned on success.
%%
`return'
%%
This is a short form for both `return_errors' and %% `return_warnings'.
%%
%% %% See {@link format_error/1} for a way to turn an error Reason to %% plain text. %% %% If the `include_as_lib' option is set, the generated code will include %% gpb.hrl as a library, which is necessary if dependencies are managed with %% Rebar. Otherwise, the header file is included directly and must be located %% in the path, which is default behaviour. %% %% The `use_packages' option instructs gpb to prepend the name of a package %% to every message it contains. If no package is defined, nothing will be %% prepended. This enables the reference of messages in other packages which %% would otherwise not be possible. However, for reasons of backward %% compatibility, this option is disabled by default. %% %% The `{msg_name_prefix,Prefix}' will add `Prefix' (a string or an atom) %% to each message. This might be useful for resolving colliding names, %% when incorporating several protocol buffer definitions into the same %% project. The `{msg_name_suffix,Suffix}' works correspondingly. %% %% The `{module_name_prefix,Prefix}' will add `Prefix' (a string or an atom) %% to the generated code and defintion files. The `{module_name_suffix,Suffix}' %% works correspondingly. file(File, Opts0) -> Opts1 = normalize_alias_opts(Opts0), Opts2 = normalize_return_report_opts(Opts1), case parse_file(File, Opts2) of {ok, Defs} -> Ext = filename:extension(File), Mod = list_to_atom( possibly_suffix_mod( possibly_prefix_mod(filename:basename(File, Ext), Opts2), Opts2)), DefaultOutDir = filename:dirname(File), Opts3 = Opts2 ++ [{o,DefaultOutDir}], proto_defs(Mod, Defs, Opts3); {error, Reason} = Error -> possibly_report_error(Error, Opts2), case proplists:get_bool(return_warnings, Opts2) of true -> {error, Reason, []}; false -> Error end end. normalize_alias_opts(Opts) -> lists:map(fun(to_msg_defs) -> to_proto_defs; ({to_msg_defs, Bool}) -> {to_proto_defs, Bool}; (Opt) -> Opt end, Opts). normalize_return_report_opts(Opts1) -> Opts2 = expand_opt(return, [return_warnings, return_errors], Opts1), Opts3 = expand_opt(report, [report_warnings, report_errors], Opts2), Opts4 = unless_defined_set(return_warnings, report_warnings, Opts3), Opts5 = unless_defined_set(return_errors, report_errors, Opts4), Opts5. expand_opt(OptionToTestFor, OptionsToExpandTo, Opts) -> case proplists:get_bool(OptionToTestFor, Opts) of true -> OptionsToExpandTo ++ delete_bool_opt(OptionToTestFor, Opts); false -> Opts end. delete_bool_opt(OptToDelete, Opts) -> %% Boolean opts can be defined both as [opt] and as [{opt, true|false}], %% delete both type of occurrences. lists:keydelete(OptToDelete, 1, Opts -- [OptToDelete]). unless_defined_set(OptionToTestFor, OptionToSet, Opts) -> case proplists:get_bool(OptionToTestFor, Opts) of true -> Opts; false -> [OptionToSet | Opts] end. possibly_prefix_mod(BaseNameNoExt, Opts) -> case proplists:get_value(module_name_prefix, Opts) of undefined -> BaseNameNoExt; Prefix -> lists:concat([Prefix, BaseNameNoExt]) end. possibly_suffix_mod(BaseNameNoExt, Opts) -> case proplists:get_value(module_name_suffix, Opts) of undefined -> BaseNameNoExt; Suffix -> lists:concat([BaseNameNoExt, Suffix]) end. %% @spec proto_defs(Mod, Defs) -> CompRet %% @equiv proto_defs(Mod, Defs, []) proto_defs(Mod, Defs) -> proto_defs(Mod, Defs, []). %% @spec proto_defs(Mod, Defs, Opts) -> CompRet %% Mod = atom() %% Defs = [Def] %% Def = {{enum, EnumName}, Enums} | %% {{msg, MsgName}, MsgFields} %% EnumName = atom() %% Enums = [{Name, integer()}] %% Name = atom() %% MsgName = atom() %% MsgFields = [#field{}] %% %% @doc %% Compile a list of pre-parsed definitions to file or to a binary. %% See {@link file/2} for information on options and return values. proto_defs(Mod, Defs0, Opts0) -> {IsAcyclic, Defs} = try_topsort_defs(Defs0), possibly_probe_defs(Defs, Opts0), {Warns, Opts1} = possibly_adjust_typespec_opt(IsAcyclic, Opts0), Opts2 = normalize_return_report_opts(Opts1), AnRes = analyze_defs(Defs, Opts2), case verify_opts(Opts2) of ok -> Res1 = do_proto_defs(Defs, clean_module_name(Mod), AnRes, Opts2), return_or_report_warnings_or_errors(Res1, Warns, Opts2, get_output_format(Opts2)); {error, OptError} -> return_or_report_warnings_or_errors({error, OptError}, [], Opts2, get_output_format(Opts2)) end. %% @spec msg_defs(Mod, Defs) -> CompRet %% @equiv msg_defs(Mod, Defs, []) %% @doc Deprecated, use proto_defs/2 instead. msg_defs(Mod, Defs) -> msg_defs(Mod, Defs, []). %% @spec msg_defs(Mod, Defs, Opts) -> CompRet %% @equiv proto_defs(Mod, Defs, Opts) %% @doc Deprecated, use proto_defs/2 instead. msg_defs(Mod, Defs, Opts) -> proto_defs(Mod, Defs, Opts). do_proto_defs(Defs, Mod, AnRes, Opts) -> case get_output_format(Opts) of proto_defs -> {ok, Defs}; binary -> ErlTxt = format_erl(Mod, Defs, AnRes, Opts), NifTxt = possibly_format_nif_cc(Mod, Defs, AnRes, Opts), compile_to_binary(Mod, Defs, ErlTxt, NifTxt, Opts); file -> ErlTxt = format_erl(Mod, Defs, AnRes, Opts), HrlTxt = possibly_format_hrl(Mod, Defs, Opts), NifTxt = possibly_format_nif_cc(Mod, Defs, AnRes, Opts), ErlOutDir = get_erl_outdir(Opts), HrlOutDir = get_hrl_outdir(Opts), NifCcOutDir = get_nif_cc_outdir(Opts), Erl = filename:join(ErlOutDir, atom_to_list(Mod) ++ ".erl"), Hrl = filename:join(HrlOutDir, atom_to_list(Mod) ++ ".hrl"), NifCc = filename:join(NifCcOutDir, atom_to_list(Mod) ++ ".nif.cc"), case {file_write_file(Erl, ErlTxt, Opts), possibly_write_file(Hrl, HrlTxt, Opts), possibly_write_file(NifCc, NifTxt, Opts)} of {ok, ok, ok} -> ok; {{error, R}, _, _} -> {error, {write_failed, Erl, R}}; {_, {error, R}, _} -> {error, {write_failed, Erl, R}}; {_, _, {error, R}} -> {error, {write_failed, NifCc, R}} end end. verify_opts(Opts) -> case {get_records_or_maps_by_opts(Opts), proplists:get_bool(nif, Opts)} of {maps, true} -> {error, {option_error, {not_supported, maps_and_nif}}}; _ -> ok end. return_or_report_warnings_or_errors(Res, ExtraWarns, Opts, OutFormat) -> Res2 = merge_warns(Res, ExtraWarns, OutFormat), possibly_report_warnings(Res2, Opts), possibly_report_error(Res2, Opts), return_warnings_or_errors(Res2, Opts). merge_warns(ok, Warns, _OutFmt) -> {ok, Warns}; merge_warns({ok, Warns1}, Warns2, file) -> {ok, Warns2++Warns1}; merge_warns({ok, Defs}, Warns, proto_defs) -> {ok, Defs, Warns}; merge_warns({ok, M, B}, Warns, binary) -> {ok, M, B, Warns}; merge_warns({ok, M, B, Warns1}, Warns2, binary) -> {ok, M, B, Warns2++Warns1}; merge_warns({error, R}, Warns, _OutFmt) -> {error, R, Warns}; merge_warns({error, R, Warns1}, Warns2, _OutFmt) -> {error, R, Warns2++Warns1}; merge_warns(error, Warns, binary) -> erlang:error({internal_error, ?MODULE, generated_code_failed_to_compile, Warns}). possibly_report_warnings(Result, Opts) -> Warns = case Result of {error, _Reason, Ws} -> Ws; {ok, _M, _B, Ws} -> Ws; {ok, _Defs, Ws} -> Ws; {ok, Ws} -> Ws end, case proplists:get_bool(report_warnings, Opts) of true -> lists:foreach(fun report_warning/1, Warns); false -> ok end. report_warning(Warn) -> io:format("~s~n", [format_warning(Warn)]). possibly_report_error(Res, Opts) -> case {Res, proplists:get_bool(report_errors, Opts)} of {{error, _Reason, _Warns}, true} -> io:format("~s~n", [format_error(Res)]); {{error, _Reason}, true} -> io:format("~s~n", [format_error(Res)]); _ -> ok end. return_warnings_or_errors(Res, Opts) -> case proplists:get_bool(return_warnings, Opts) of true -> Res; false -> case Res of {ok, Mod, Bin, _Warns} -> {ok, Mod, Bin}; {ok, MsgDefs, _Warns} -> {ok, MsgDefs}; {ok, _Warns} -> ok; {error, R, _Warns} -> {error, R} end end. get_output_format([binary | _]) -> binary; get_output_format([{binary, true} | _]) -> binary; get_output_format([to_proto_defs | _]) -> proto_defs; get_output_format([{to_proto_defs, true} | _]) -> proto_defs; get_output_format([_ | Rest]) -> get_output_format(Rest); get_output_format([]) -> file. get_erl_outdir(Opts) -> proplists:get_value(o_erl, Opts, get_outdir(Opts)). get_hrl_outdir(Opts) -> proplists:get_value(o_hrl, Opts, get_outdir(Opts)). get_nif_cc_outdir(Opts) -> proplists:get_value(o_nif_cc, Opts, get_outdir(Opts)). get_outdir(Opts) -> proplists:get_value(o, Opts, "."). clean_module_name(Mod) -> Clean = re:replace(atom_to_list(Mod), "[.]", "_", [global, {return,list}]), list_to_atom(Clean). %% @spec format_error({error, Reason} | Reason) -> io_list() %% Reason = term() %% %% @doc Produce a plain-text error message from a reason returned by %% for instance {@link file/2} or {@link proto_defs/2}. format_error({error, Reason, _Warns}) -> fmt_err(Reason); format_error({error, Reason}) -> fmt_err(Reason); format_error(Reason) -> fmt_err(Reason). %% Note: do NOT include trailing newline (\n or ~n) fmt_err({option_error, {not_supported, maps_and_nif}}) -> ?f("Options maps and nif are mutually exclusive"); fmt_err({parse_error, FileName, {Line, Module, ErrInfo}}) -> ?f("~s:~w: ~s", [FileName, Line, Module:format_error(ErrInfo)]); fmt_err({scan_error, FileName, {Line, Module, ErrInfo}}) -> ?f("~s:~w: ~s", [FileName, Line, Module:format_error(ErrInfo)]); fmt_err({import_not_found, Import}) -> ?f("Could not find import file ~p", [Import]); fmt_err({read_failed, File, Reason}) -> ?f("failed to read ~p: ~s (~p)", [File, file:format_error(Reason), Reason]); fmt_err({post_process, Reasons}) -> gpb_parse:format_post_process_error({error, Reasons}); fmt_err({write_failed, File, Reason}) -> ?f("failed to write ~s: ~s (~p)", [File, file:format_error(Reason),Reason]); fmt_err(X) -> ?f("Unexpected error ~p", [X]). %% @spec format_warning(Reason) -> io_list() %% Reason = term() %% %% @doc Produce a plain-text error message from a reason returned by %% for instance {@link file/2} or {@link proto_defs/2}. %% @end %% Note: do NOT include trailing newline (\n or ~n) format_warning(cyclic_message_dependencies) -> ?f("Warning: omitting type specs due to cyclic message references."); format_warning(X) -> case io_lib:deep_char_list(X) of true -> X; false -> ?f("Warning: Unknown warning: ~p", [X]) end. %% @doc Command line interface for the compiler. %% With no proto file to compile, print a help message and exit. -spec c() -> no_return(). c() -> c([undefined]). %% @doc This function is intended as a command line interface for the compiler. %% Call it from the command line as follows: %% ``` %% erl [gpb-opts] -s gpb_compile c File.proto ... %% erl -s gpb_compile c File.proto ... -extra [gpb-opts] %% ''' %% The `' can be `-noshell -noinput +B -boot start_clean -pa SomeDir' %% %% The following options are supported: %%
%%
`-IDir' `-I Dir'
%%
Specify include directory. %% Option may be specified more than once to specify %% several include directories.
%%
`-o Dir'
%%
Specify output directory for where to generate %% the ProtoFile.erl and ProtoFile.hrl
%%
`-o-erl Dir' | `-o-hrl Dir' | `-o-nif-cc Dir'
%%
Specify output directory for where to generate %% the ProtoFile.erl and ProtoFile.hrl respectively, %% and for the NIF C++ file, if the `-nif' option is specified. %% The `-o-erl Dir' option overrides any `-o Dir' option, and %% similarly for the other file-type specific output options.
%%
`-v optionally | always | never'
%%
Specify how the generated encoder should %% verify the message to be encoded.
%%
`-nif'
%%
Generate nifs for linking with the protobuf C(++) library.
%%
`-load_nif FunctionDefinition'
%%
Specify `FunctionDefinition' as the text that defines the %% function `load_nif/0'. This is called as the `on_load' %% hook for loading the NIF. See also the doc for the `load_nif' %% option in the {@link file/2} function.
%%
`-c true | false | auto | integer() | float()'
%%
Specify how or when the generated decoder should %% copy fields of type `bytes'. See the `copy_bytes' option %% for the function {@link file/2} for more info.
%%
`-strbin'
%%
Specify that decoded strings should be returend as binaries, %% instead of as strings (lists).
%%
`-pldefs'
%%
Specify that introspection functions shall return proplists %% instead of `#field{}' records, to make the generated code %% completely free of even compile-time dependencies to gpb.
%%
`-msgprefix Prefix'
%%
Prefix each message with `Prefix'. This can be useful to %% when including different sub-projects that have colliding %% message names.
%%
`-modprefix Prefix'
%%
Prefix each module with `Prefix'. Normally the module name of %% the generated code is based on the name of the `.proto' file. %% This option prepends a prefix to the module name, which can be %% useful when including different sub-projects that have %% colliding proto file names.
%%
`-msgsuffix Suffix'
%%
Sufffix each message name with `Suffix'.
%%
`-modsuffix Suffix'
%%
Suffix each module name with `Suffix'.
%%
`-il'
%%
Generate code that include gpb.hrl using `-include_lib' %% instad of `-include', which is the default.
%%
`-type'
%%
Enables `::Type()' annotations in the generated .hrl file.
%%
`-descr'
%%
Generate self-description information.
%%
`-maps'
%%
Generate code that will accept and produce maps instead of %% records. No .hrl file will be generated. See the `maps' option %% for the function {@link file/2} for more info.
%%
`--help' or `-h'
%%
Show help.
%%
`--version' or `-V'
%%
Show the version number of gpb.
%%
%% If several files are specified, each is compiled individually, no %% checking is done for instance for multiply defined messages or %% fields across files, such as the `protoc' does. -spec c([string() | atom()]) -> no_return(). c([F | _]=Files) when is_atom(F); is_list(F) -> %% invoked with -s or -run erlang:system_flag(backtrace_depth, 32), FileNames = [if File == undefined -> undefined; is_atom(File) -> atom_to_list(File); is_list(File) -> File end || File <- Files], Args = init:get_arguments(), PlainArgs = init:get_plain_arguments(), Opts1 = parse_opts(Args, PlainArgs), Opts2 = [report_warnings, report_errors] ++ Opts1, Results = [case determine_cmdline_op(Opts2, FileName) of error -> show_help(), halt(1); show_help -> show_help(), halt(0); show_version -> show_version(), halt(0); compile -> file(FileName, Opts2) end || FileName <- FileNames], case lists:usort(Results) of [ok] -> halt(0); _Errs -> halt(1) end, timer:sleep(infinity). %% give init:stop time to do its work opt_specs() -> [ {"I", string_maybe_appended, i, "\n" " Specify include directory.\n" " Option may be specified more than once to specify\n" " several include directories.\n"}, {"o", string, o, "Dir\n" " Specify output directory for where to generate\n" " the .erl and .hrl\n"}, {"o-erl", string, o_erl, "Dir\n" " Specify output directory for where to generate\n" " the .erl.\n" " The -o-erl Dir option overrides any -o Dir option, and\n" " similarly for the other file-type specific output options.\n"}, {"o-hrl", string, o_hrl, "Dir\n" " Specify output directory for where to generate\n" " the .hrl\n"}, {"o-nif-cc", string, o_nif_cc, "Dir\n" " Specify output directory for where to generate\n" " the NIF C++ file, if the -nif option is specified\n"}, {"nif", undefined, nif, "\n" " Generate nifs for linking with the protobuf C(++) library.\n"}, {"load_nif", string, load_nif, "FunctionDefinition\n" " Specify FunctionDefinition as the text that defines the\n" " function load_nif/0. This is called as the -on_load.\n" " hook for loading the NIF.\n"}, {"v", {optionally, always, never}, verify, " optionally | always | never\n" " Specify how the generated encoder should\n" " verify the message to be encoded.\n"}, {"c", {true, false, auto, integer, float}, copy_bytes, " true | false | auto | number() \n" " Specify how or when the generated decoder should\n" " copy fields of type bytes.\n"}, {"strbin", undefined, strings_as_binaries, "\n" " Specify that decoded strings should be returend as binaries,\n" " instead of as strings (lists).\n"}, {"pldefs", undefined, defs_as_proplists, "\n" " Specify that introspection functions shall return proplists\n" " instead of #field{} records, to make the generated code\n" " completely free of even compile-time dependencies to gpb.\n"}, {"msgprefix", string, msg_name_prefix, "Prefix\n" " Prefix each message with Prefix.\n"}, {"modprefix", string, module_name_prefix, "Prefix\n" " Prefix the module name with Prefix.\n"}, {"msgsuffix", string, msg_name_suffix, "Suffix\n" " Suffix each message with Suffix.\n"}, {"modsuffix", string, module_name_suffix, "Suffix\n" " Suffix the module name with Suffix.\n"}, {"il", undefined, include_as_lib, "\n" " Generate code that includes gpb.hrl using -include_lib\n" " instead of -include, which is the default.\n"}, {"type", undefined, type_specs, "\n" " Enables `::Type()' annotations in the generated .hrl file.\n"}, {"descr", undefined, descriptor, "\n" " Generate self-description information.\n"}, {"maps", undefined, maps, "\n" " Generate code that will accept and produce maps instead of\n" " records.\n"}, {"h", undefined, help, "\n" " Show help\n"}, {"-help", undefined, help, "\n" " Show help\n"}, {"V", undefined, version, "\n" " Show version\n"}, {"-version", undefined, version, "\n" " Show version\n"} ]. find_opt_spec(Opt) -> lists:filter(fun({OptDef, OptType, _, _}) -> % the type of comparison depends on the opt spec type case OptType of % if the opt arg may be appended to the option % (ie. -Iinclude1) string_maybe_appended -> case re:run(Opt, "^"++OptDef) of {match, _} -> true; nomatch -> false end; % for the other cases an exact match is required _ -> Opt == OptDef end end, opt_specs()). determine_cmdline_op(Opts, FileName) -> Help = lists:member(help, Opts) orelse FileName == "-h" orelse FileName == "--help", Vsn = lists:member(version, Opts) orelse FileName == "-V" orelse FileName == "--version", case {Help, Vsn} of {true, _} -> show_help; {_, true} -> show_version; _ -> if FileName == undefined -> error; is_list(FileName) -> compile end end. show_help() -> io:format( "gpb version ~s~n" "Usage: erl [gpb-opts] -s ~p c .proto~n" " or: erl -s ~p c .proto -extra [gpb-opts]~n" "Typical erlargs = -noshell -noinput +B -boot start_clean -pa SomeDir~n" "~n", [gpb:version_as_string(), ?MODULE, ?MODULE]), show_args(). show_arg({OptDef, string_maybe_appended, _, OptDoc}) -> io:format(" -~s -~sOption ~s", [OptDef, OptDef, OptDoc]); show_arg({OptDef, _, _, OptDoc}) -> io:format(" -~s ~s", [OptDef, OptDoc]). show_args() -> io:format( "Recognized gpb-opts: (see the edoc for ~p for further details)~n", [?MODULE]), lists:foreach(fun show_arg/1, opt_specs()). show_version() -> io:format("gpb version ~s~n", [gpb:version_as_string()]). parse_opts(Args, PlainArgs) -> arg_zf(fun parse_opt/1, Args) ++ plain_arg_zf(fun parse_opt/1, PlainArgs). parse_opt({Opt, OptArg}) -> parse_opt_spec(find_opt_spec(Opt), Opt, OptArg). parse_opt_spec([{OptDef, string_maybe_appended, OptErl, _}], Opt, OptArg) -> % now check what is the form,