Current section

Files

Jump to
protobuf mix.exs
Raw

mix.exs

defmodule Protobuf.Mixfile do
use Mix.Project
@source_url "https://github.com/elixir-protobuf/protobuf"
@version "0.9.0"
@description "A pure Elixir implementation of Google Protobuf."
def project do
[
app: :protobuf,
version: @version,
elixir: "~> 1.6",
build_embedded: Mix.env() == :prod,
start_permanent: Mix.env() == :prod,
dialyzer: [plt_add_apps: [:mix, :jason]],
elixirc_paths: elixirc_paths(Mix.env()),
test_coverage: [tool: ExCoveralls],
preferred_cli_env: [
coveralls: :test,
"coveralls.html": :test,
conformance_test: :conformance
],
deps: deps(),
escript: escript(Mix.env()),
description: @description,
package: package(),
docs: docs(),
aliases: aliases()
]
end
def application do
[
extra_applications: [:logger, :eex]
]
end
defp elixirc_paths(:test), do: ["lib", "test/support", "generated"]
defp elixirc_paths(:conformance), do: ["lib", "conformance", "generated"]
defp elixirc_paths(_), do: ["lib"]
defp deps do
[
{:jason, "~> 1.2", optional: true},
{:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false},
{:credo, "~> 1.5", only: [:dev, :test], runtime: false},
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
{:stream_data, "~> 0.5.0", only: [:dev, :test, :conformance]},
{:excoveralls, "~> 0.14.4", only: :test},
{:google_protobuf,
github: "protocolbuffers/protobuf",
tag: "v3.19.1",
app: false,
compile: false,
only: [:dev, :test, :conformance]}
]
end
defp escript(:conformance) do
[main_module: Conformance.Protobuf.Runner, name: "conformance_client"]
end
defp escript(_env) do
[main_module: Protobuf.Protoc.CLI, name: "protoc-gen-elixir"]
end
defp package do
[
maintainers: ["Bing Han", "Andrea Leopardi"],
licenses: ["MIT"],
files: ~w(
mix.exs
README.md
lib/google
lib/protobuf
lib/*.ex
src
LICENSE
priv/templates
.formatter.exs
),
links: %{"GitHub" => @source_url}
]
end
defp docs do
[
extras: ["README.md"],
main: "readme",
source_url: @source_url,
source_ref: "v#{@version}"
]
end
defp aliases do
[
gen_bootstrap_protos: [&build_escript/1, &gen_bootstrap_protos/1],
gen_test_protos: [&build_escript/1, &create_generated_dir/1, &gen_test_protos/1],
test: [
&build_escript/1,
&create_generated_dir/1,
&gen_test_protos/1,
&gen_google_test_protos/1,
"test"
],
conformance_test: [
fn _ ->
Mix.shell().cmd("mix escript.build", env: %{"MIX_ENV" => "test"})
end,
&create_generated_dir/1,
&gen_google_test_protos/1,
&gen_conformance_protos/1,
"escript.build",
&run_conformance_tests/1
],
gen_bench_protos: [&build_escript/1, &gen_bench_protos/1]
]
end
# We need to do this in a separate shell because we want to compile "from scratch" when we do
# things like running tests, since we usually generated some .pb.ex files on the fly and stick
# them in a directory. That directory is in the elixirc_paths, but no files are there *before*
# we compile the project the first time to run escript.build. It's a chicken and egg problem.
# This way, we compile the project first to generate the escript and we do so in a subshell.
# Then, we generate the .pb.ex files. Then we actually run stuff (like "mix test"), so that
# compilation happens again "from scratch" and it picks up the generated files. This fixes at
# least one problem related to extensions, because extensions are discovered by looking at
# :application.get_key(app, :modules), and if modules are added after the first compilation and
# loading pass, then they don't get picked up in there.
# There is very likely a better way to do this, but this works for now.
defp build_escript(_args) do
# We wanna pass down MIX_ENV here because we want escript.build to happen in the same Mix
# environment of whoever is calling this function.
Mix.shell().cmd("mix escript.build", env: %{"MIX_ENV" => Atom.to_string(Mix.env())})
end
defp create_generated_dir(_args) do
File.mkdir_p!("generated")
end
# These files are necessary to bootstrap the protoc-gen-elixir plugin that we generate. They
# are committed to version control.
defp gen_bootstrap_protos(_args) do
proto_src = path_in_protobuf_source(["src", "google", "protobuf"])
protoc!("-I \"#{proto_src}\" --elixir_out=lib/google", ["descriptor.proto"])
protoc!("-I \"#{proto_src}\" --elixir_out=lib/google", ["compiler/plugin.proto"])
protoc!("-I src --elixir_out=lib", ["elixirpb.proto"])
Mix.Task.rerun("format", ["lib/elixirpb.pb.ex", "lib/google/**/*.pb.ex"])
end
defp gen_test_protos(_args) do
protoc!(
"-I src -I test/protobuf/protoc/proto --elixir_out=generated",
["test/protobuf/protoc/proto/extension.proto"]
)
protoc!(
"-I test/protobuf/protoc/proto --elixir_opt=package_prefix=my --elixir_out=generated",
["test/protobuf/protoc/proto/test.proto"]
)
protoc!(
"-I test/protobuf/protoc/proto -I #{path_in_protobuf_source("src")} --elixir_out=generated --elixir_opt=gen_descriptors=true",
["test/protobuf/protoc/proto/custom_options.proto"]
)
Mix.Task.rerun("format", ["generated/**/*.pb.ex"])
end
defp gen_bench_protos(_args) do
proto_bench = path_in_protobuf_source(["benchmarks"])
protoc!("-I \"#{proto_bench}\" --elixir_out=./bench/lib", benchmark_proto_files())
Mix.Task.rerun("format", ["bench/**/*.pb.ex"])
end
defp gen_google_test_protos(_args) do
proto_root = path_in_protobuf_source(["src"])
files = ~w(
google/protobuf/any.proto
google/protobuf/duration.proto
google/protobuf/empty.proto
google/protobuf/field_mask.proto
google/protobuf/struct.proto
google/protobuf/timestamp.proto
google/protobuf/wrappers.proto
google/protobuf/test_messages_proto2.proto
google/protobuf/test_messages_proto3.proto
)
protoc!("--elixir_out=./generated -I \"#{proto_root}\"", files)
Mix.Task.rerun("format", ["generated/**/*.pb.ex"])
end
defp gen_conformance_protos(_args) do
proto_src = path_in_protobuf_source(["conformance"])
protoc!("--elixir_out=./generated -I \"#{proto_src}\"", ["conformance.proto"])
Mix.Task.rerun("format", ["generated/**/*.pb.ex"])
end
defp path_in_protobuf_source(path) do
Path.join([Mix.Project.deps_paths().google_protobuf | List.wrap(path)])
end
defp benchmark_proto_files do
[
"benchmarks.proto",
"datasets/google_message1/proto3/benchmark_message1_proto3.proto",
"datasets/google_message1/proto2/benchmark_message1_proto2.proto",
"datasets/google_message2/benchmark_message2.proto",
"datasets/google_message3/benchmark_message3.proto",
"datasets/google_message3/benchmark_message3_1.proto",
"datasets/google_message3/benchmark_message3_2.proto",
"datasets/google_message3/benchmark_message3_3.proto",
"datasets/google_message3/benchmark_message3_4.proto",
"datasets/google_message3/benchmark_message3_5.proto",
"datasets/google_message3/benchmark_message3_6.proto",
"datasets/google_message3/benchmark_message3_7.proto",
"datasets/google_message3/benchmark_message3_8.proto",
"datasets/google_message4/benchmark_message4.proto",
"datasets/google_message4/benchmark_message4_1.proto",
"datasets/google_message4/benchmark_message4_2.proto",
"datasets/google_message4/benchmark_message4_3.proto"
]
end
defp protoc!(args, files_to_generate) when is_binary(args) and is_list(files_to_generate) do
cmd = "protoc --plugin=./protoc-gen-elixir #{args} #{Enum.join(files_to_generate, " ")}"
Mix.shell().info([:cyan, "Running: ", :reset, cmd])
case Mix.shell().cmd(cmd) do
0 -> :ok
other -> Mix.raise("protoc exited with non-zero status: #{other}")
end
end
defp run_conformance_tests(args) do
{options, _args} = OptionParser.parse!(args, strict: [runner: :string, verbose: :boolean])
runner =
options
|> Keyword.get_lazy(:runner, fn -> Mix.raise("Missing required option --runner") end)
|> Path.expand()
verbose? = Keyword.get(options, :verbose, false)
args = [
"--enforce_recommended",
"--failure_list",
"conformance/exemptions.txt",
"./conformance_client"
]
args = if verbose?, do: ["--verbose"] ++ args, else: args
case Mix.shell().cmd("#{runner} #{Enum.join(args, " ")}", stderr_to_stdout: true) do
0 -> :ok
other -> Mix.raise("#{runner} exited with non-zero status: #{other}")
end
end
end