diff --git a/Makefile b/Makefile index 6fbacd16..f61ce57b 100644 --- a/Makefile +++ b/Makefile @@ -16,4 +16,4 @@ gen-protos: protoc-gen-elixir protoc -I src -I test/protobuf/protoc/proto --elixir_out=test/protobuf/protoc/proto_gen --plugin=./protoc-gen-elixir test/protobuf/protoc/proto/*.proto protoc -I src --elixir_out=lib --plugin=./protoc-gen-elixir elixirpb.proto -.PHONY: clean gen_google_proto gen_test_protos +.PHONY: clean protoc-gen-elixir gen-google-protos gen-protos diff --git a/lib/mix/tasks/protobuf.ex b/lib/mix/tasks/protobuf.ex new file mode 100644 index 00000000..189e202e --- /dev/null +++ b/lib/mix/tasks/protobuf.ex @@ -0,0 +1,7 @@ +defmodule Mix.Tasks.Protobuf do + use Mix.Task + + def run(args) do + Protobuf.Protoc.CLI.main(args) + end +end diff --git a/lib/protobuf/dsl.ex b/lib/protobuf/dsl.ex index caf33cd2..0f656bd4 100644 --- a/lib/protobuf/dsl.ex +++ b/lib/protobuf/dsl.ex @@ -258,6 +258,10 @@ defmodule Protobuf.DSL do |> cal_encoded_fnum() end + defp parse_field_opts([{:extensions, map} | t], acc) do + parse_field_opts(t, Map.put(acc, :extensions, map)) + end + defp parse_field_opts([{:optional, true} | t], acc) do parse_field_opts(t, Map.put(acc, :optional?, true)) end diff --git a/lib/protobuf/field_props.ex b/lib/protobuf/field_props.ex index 693bed75..b0a05a2e 100644 --- a/lib/protobuf/field_props.ex +++ b/lib/protobuf/field_props.ex @@ -17,7 +17,8 @@ defmodule Protobuf.FieldProps do packed?: boolean, map?: boolean, deprecated?: boolean, - encoded_fnum: iodata + encoded_fnum: iodata, + extensions: map } defstruct fnum: nil, name: nil, @@ -34,5 +35,6 @@ defmodule Protobuf.FieldProps do packed?: nil, map?: false, deprecated?: false, - encoded_fnum: nil + encoded_fnum: nil, + extensions: %{} end diff --git a/lib/protobuf/protoc/cli.ex b/lib/protobuf/protoc/cli.ex index 58a15047..4f7e7473 100644 --- a/lib/protobuf/protoc/cli.ex +++ b/lib/protobuf/protoc/cli.ex @@ -18,6 +18,7 @@ defmodule Protobuf.Protoc.CLI do @doc false def main(["--version"]) do + Application.ensure_all_started(:protobuf) {:ok, version} = :application.get_key(:protobuf, :vsn) IO.puts(to_string(version)) end @@ -27,6 +28,9 @@ defmodule Protobuf.Protoc.CLI do end def main(_) do + Application.ensure_all_started(:protobuf) + load_custom_extensions() + # https://groups.google.com/forum/#!topic/elixir-lang-talk/T5enez_BBTI :io.setopts(:standard_io, encoding: :latin1) bin = IO.binread(:all) @@ -134,4 +138,14 @@ defmodule Protobuf.Protoc.CLI do |> Enum.filter(&(&1 && &1 != "")) |> Enum.join(".") end + + defp load_custom_extensions do + Application.get_env(:protobuf, :extension_paths, []) + |> Enum.map(&Path.wildcard/1) + |> List.flatten() + |> Enum.map(&Code.require_file/1) + |> List.flatten() + |> Enum.map(fn {module, _} -> module end) + |> Protobuf.Extension.cal_extensions() + end end diff --git a/lib/protobuf/protoc/generator/message.ex b/lib/protobuf/protoc/generator/message.ex index 33d631a5..9bc94ebf 100644 --- a/lib/protobuf/protoc/generator/message.ex +++ b/lib/protobuf/protoc/generator/message.ex @@ -288,6 +288,13 @@ defmodule Protobuf.Protoc.Generator.Message do end defp merge_field_options(opts, f) do + opts = + if map_size(f.options.__pb_extensions__) > 0 do + Map.put(opts, :extensions, inspect(f.options.__pb_extensions__)) + else + opts + end + opts |> Map.put(:packed, f.options.packed) |> Map.put(:deprecated, f.options.deprecated) diff --git a/test/protobuf/protoc/generator/message_test.exs b/test/protobuf/protoc/generator/message_test.exs index 605b1780..52b80330 100644 --- a/test/protobuf/protoc/generator/message_test.exs +++ b/test/protobuf/protoc/generator/message_test.exs @@ -143,13 +143,20 @@ defmodule Protobuf.Protoc.Generator.MessageTest do number: 1, type: :TYPE_INT32, label: :LABEL_OPTIONAL, - options: Google.Protobuf.FieldOptions.new(packed: true) + options: + Google.Protobuf.FieldOptions.new( + packed: true, + __pb_extensions__: %{{Mypkg.PbExtension, :myopt_bool} => true} + ) ) ] ) {[], [msg]} = Generator.generate(ctx, desc) - assert msg =~ "field :a, 1, optional: true, type: :int32, packed: true\n" + + assert msg =~ + "field :a, 1, optional: true, type: :int32, " <> + "extensions: %{{Mypkg.PbExtension, :myopt_bool} => true}, packed: true\n" end test "generate/2 supports option :deprecated" do diff --git a/test/protobuf/protoc/integration_test.exs b/test/protobuf/protoc/integration_test.exs index f4f70498..00c51dee 100644 --- a/test/protobuf/protoc/integration_test.exs +++ b/test/protobuf/protoc/integration_test.exs @@ -50,6 +50,10 @@ defmodule Protobuf.Protoc.IntegrationTest do test "options" do assert %{deprecated?: true} = My.Test.Options.__message_props__().field_props[1] + + assert %{ + extensions: %{{Mypkg.PbExtension, :myopt_bool} => true} + } = My.Test.Options.__message_props__().field_props[2] end test "extensions" do diff --git a/test/protobuf/protoc/proto/extension.proto b/test/protobuf/protoc/proto/extension.proto index 6d3be1f4..e6b239ca 100644 --- a/test/protobuf/protoc/proto/extension.proto +++ b/test/protobuf/protoc/proto/extension.proto @@ -4,9 +4,12 @@ package ext; // -I src is needed, see Makefile import "elixirpb.proto"; +import "mypkg.proto"; option (elixirpb.file).module_prefix = "Protobuf.Protoc.ExtTest"; message Foo { optional string a = 1; + optional string b = 2 [(mypkg.myopt_bool)=true]; + optional string c = 3 [(mypkg.myopt_string)="test"]; } diff --git a/test/protobuf/protoc/proto/mypkg.proto b/test/protobuf/protoc/proto/mypkg.proto new file mode 100644 index 00000000..3a1d6434 --- /dev/null +++ b/test/protobuf/protoc/proto/mypkg.proto @@ -0,0 +1,10 @@ +syntax = "proto2"; + +package mypkg; + +import "google/protobuf/descriptor.proto"; + +extend google.protobuf.FieldOptions { + optional bool myopt_bool = 100000; + optional string myopt_string = 100001 [default="test"]; +} diff --git a/test/protobuf/protoc/proto/test.proto b/test/protobuf/protoc/proto/test.proto index 227ad428..d0773372 100644 --- a/test/protobuf/protoc/proto/test.proto +++ b/test/protobuf/protoc/proto/test.proto @@ -6,6 +6,9 @@ package my.test; // dotted package name //import "imp.proto"; import "multi/multi1.proto"; // unused import +// extensions +import "mypkg.proto"; + enum HatType { // deliberately skipping 0 FEDORA = 1; @@ -125,4 +128,5 @@ message Communique { message Options { optional string opt1 = 1 [deprecated=true]; + optional string opt2 = 2 [(mypkg.myopt_bool)=true]; } diff --git a/test/protobuf/protoc/proto_gen/extension.pb.ex b/test/protobuf/protoc/proto_gen/extension.pb.ex index 8fbddfe9..4f5a989d 100644 --- a/test/protobuf/protoc/proto_gen/extension.pb.ex +++ b/test/protobuf/protoc/proto_gen/extension.pb.ex @@ -3,9 +3,21 @@ defmodule Protobuf.Protoc.ExtTest.Foo do use Protobuf, syntax: :proto2 @type t :: %__MODULE__{ - a: String.t() + a: String.t(), + b: String.t(), + c: String.t() } - defstruct [:a] + defstruct [:a, :b, :c] field :a, 1, optional: true, type: :string + + field :b, 2, + optional: true, + type: :string, + extensions: %{{Mypkg.PbExtension, :myopt_bool} => true} + + field :c, 3, + optional: true, + type: :string, + extensions: %{{Mypkg.PbExtension, :myopt_string} => "test"} end diff --git a/test/protobuf/protoc/proto_gen/mypkg.pb.ex b/test/protobuf/protoc/proto_gen/mypkg.pb.ex new file mode 100644 index 00000000..1bbab361 --- /dev/null +++ b/test/protobuf/protoc/proto_gen/mypkg.pb.ex @@ -0,0 +1,11 @@ +defmodule Mypkg.PbExtension do + @moduledoc false + use Protobuf, syntax: :proto2 + + extend Google.Protobuf.FieldOptions, :myopt_bool, 100_000, optional: true, type: :bool + + extend Google.Protobuf.FieldOptions, :myopt_string, 100_001, + optional: true, + type: :string, + default: "test" +end diff --git a/test/protobuf/protoc/proto_gen/test.pb.ex b/test/protobuf/protoc/proto_gen/test.pb.ex index ecb9a8e6..246ffe95 100644 --- a/test/protobuf/protoc/proto_gen/test.pb.ex +++ b/test/protobuf/protoc/proto_gen/test.pb.ex @@ -246,11 +246,17 @@ defmodule My.Test.Options do use Protobuf, syntax: :proto2 @type t :: %__MODULE__{ - opt1: String.t() + opt1: String.t(), + opt2: String.t() } - defstruct [:opt1] + defstruct [:opt1, :opt2] field :opt1, 1, optional: true, type: :string, deprecated: true + + field :opt2, 2, + optional: true, + type: :string, + extensions: %{{Mypkg.PbExtension, :myopt_bool} => true} end defmodule My.Test.PbExtension do