From 59a3275ee75fc60bf9aa481608d08d554d6eaf59 Mon Sep 17 00:00:00 2001 From: Alexandre Hamez Date: Sat, 4 Jul 2020 17:07:25 +0200 Subject: [PATCH] Add mix task to generate files --- .travis.yml | 2 -- README.md | 16 +++++++++++++++- coveralls.json | 3 ++- lib/mix/tasks/protox/generate.ex | 25 +++++++++++++++++++++++++ lib/protox.ex | 21 +++++++++++++++++++++ test/protox_test.exs | 11 +++++++++++ 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 lib/mix/tasks/protox/generate.ex diff --git a/.travis.yml b/.travis.yml index 020037f8..0aa07b8c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: elixir matrix: include: - - elixir: 1.6 - otp_release: 21.0 - elixir: 1.7 otp_release: 22.0 - elixir: 1.8 diff --git a/README.md b/README.md index a687eeee..6ca8c46b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ Protox is an Elixir library to work with [Google's Protocol Buffers](https://dev Generally speaking, a lot of efforts have been put into making sure that the library is reliable (for instance using [property based testing](https://github.com/alfert/propcheck) and by having a [100% code coverage](https://coveralls.io/github/ahamez/protox?branch=master)). Therefore, this library passes all the tests of the conformance checker provided by Google. See [Conformance](#conformance) section for more information. -This library is easy to use: you just point to the `*.proto` files or give the schema to the `Protox` macro, no need to generate any file! Furthermore, it provides a full-blown Elixir experience with protobuf messages. For instance, given the following protobuf `msg.proto` file: +This library is easy to use: you just point to the `*.proto` files or give the schema to the `Protox` macro, no need to generate any file! However, should you need to generate files, a mix task is available (see [Files generation](#files-generation)). + +This library also provides a full-blown Elixir experience with protobuf messages. For instance, given the following protobuf `msg.proto` file: ```proto syntax = "proto3"; @@ -292,6 +294,18 @@ When you encode a message that contains unknown fields, they will be reencoded i The detailed reference of the generated code is available [here](documentation/reference.md). +## Files generation + +It's also possible to generate a file that will contain all code corresponding to the protobuf messages: + +```shell +MIX_ENV=prod mix protox.generate --output-path=/path/to/message.ex --include-path=. test/messages.proto test/samples/proto2.proto +``` + +The `--include-path` option is the same as the option described in section [Specify import path](#specify-import-path). + +The generated file will be usable in any project as long as protox is declared in the dependancies (the generated file is not a standalone, it still needs function from the protox runtime). + ## Types mapping The following table shows how Protobuf types are mapped to Elixir's ones. diff --git a/coveralls.json b/coveralls.json index 4228d57a..22622d2d 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,6 +1,7 @@ { "skip_files": [ "conformance", - "benchmarks" + "benchmarks", + "generate.ex" ] } diff --git a/lib/mix/tasks/protox/generate.ex b/lib/mix/tasks/protox/generate.ex new file mode 100644 index 00000000..50ee6ce4 --- /dev/null +++ b/lib/mix/tasks/protox/generate.ex @@ -0,0 +1,25 @@ +defmodule Mix.Tasks.Protox.Generate do + @moduledoc false + + use Mix.Task + + @impl Mix.Task + @spec run(any) :: any + def run(args) do + with {options, files, []} <- + OptionParser.parse(args, strict: [output_path: :string, include_path: :string]), + {:ok, output_path} <- Keyword.fetch(options, :output_path), + include_path <- Keyword.get(options, :include_path) do + generate_code(files, output_path, include_path) + else + err -> + IO.puts("Failed to generate code: #{inspect(err)}") + :error + end + end + + defp generate_code(files, output_path, include_path) do + code = Protox.generate_code(files, include_path) + File.write!(output_path, code) + end +end diff --git a/lib/protox.ex b/lib/protox.ex index 4af00e96..0f41305f 100644 --- a/lib/protox.ex +++ b/lib/protox.ex @@ -68,6 +68,27 @@ defmodule Protox do end end + def generate_code(files, include_path \\ nil) do + path = + case include_path do + nil -> nil + _ -> Path.expand(include_path) + end + + {:ok, file_descriptor_set} = + files + |> Enum.map(&Path.expand/1) + |> Protox.Protoc.run(path) + + {enums, messages} = Protox.Parse.parse(file_descriptor_set, nil) + + code = quote do: unquote(Protox.Define.define(enums, messages)) + + code_str = Macro.to_string(code) + + ["#", " credo:disable-for-this-file\n", code_str] + end + defp make_external_resources(files) do Enum.map(files, fn file -> quote(do: @external_resource(unquote(file))) end) end diff --git a/test/protox_test.exs b/test/protox_test.exs index 8508722d..9f20878c 100644 --- a/test/protox_test.exs +++ b/test/protox_test.exs @@ -256,6 +256,17 @@ defmodule ProtoxTest do assert msg == msg |> Camel.encode!() |> :binary.list_to_bin() |> Camel.decode!() end + test "Generate code" do + file = Path.join(__DIR__, "./samples/prefix/bar/bar.proto") + str = Protox.generate_code([file], "./test/samples") + + tmp_dir = System.tmp_dir!() + tmp_file = Path.join(tmp_dir, "generated_code.ex") + File.write!(tmp_file, str) + + assert Code.compile_file(tmp_file) != [] + end + @tag conformance: true test "Launch conformance" do {:ok, _} = File.rm_rf("./failing_tests.txt")