Skip to main content

mix.exs

defmodule GrpcReflection.MixProject do
  use Mix.Project

  @version "0.5.0"
  @source_url "https://github.com/elixir-grpc/grpc-reflection"
  @description "gRPC reflection server for Elixir"

  def project do
    [
      app: :grpc_reflection,
      version: @version,
      description: @description,
      elixir: "~> 1.15",
      elixirc_paths: elixirc_paths(Mix.env()),
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      package: package(),
      docs: docs(),
      aliases: aliases(),
      test_coverage: [
        ignore_modules: [
          ~r/^Grpc\./,
          ~r/^Google\./,
          ~r/^ScalarTypes\./,
          ~r/^Streaming\./,
          ~r/^EmptyService\./,
          ~r/^EdgeCases\./,
          ~r/^Proto2Features\./,
          ~r/^CustomizedPrefix\./,
          ~r/^ImportsTest\./,
          ~r/^CommonTypes\./,
          ~r/^GlobalService$/,
          ~r/^GlobalRequest$/,
          ~r/^GlobalResponse$/,
          ~r/^Nested\./,
          ~r/^WellKnownTypes\./,
          ~r/^PackageA\./,
          ~r/^PackageB\./,
          ~r/^NestedEnumConflict\./,
          ~r/^RecursiveMessage\./,
          ~r/^NoDescriptor\./,
          GrpcReflection.TestEndpoint,
          GrpcReflection.TestEndpoint.Endpoint
        ]
      ],
      dialyzer: dialyzer()
    ]
  end

  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger]
    ]
  end

  # Run "mix help deps" to learn about dependencies.
  defp deps do
    [
      {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
      {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
      {:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
      {:grpc, "~> 1.0"},
      {:grpc_server, "~> 1.0"},
      # grpc 1.0 made the HTTP client adapters optional; the test suite connects
      # real stubs to the in-process server, so it needs an adapter (Gun).
      {:gun, "~> 2.0", only: :test},
      {:protobuf, "~> 0.17"}
    ]
  end

  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

  defp aliases do
    [
      build_protos: [&build_protos/1],
      check: ["dialyzer", "credo --strict"]
    ]
  end

  @protoc_opts "gen_descriptors=true,gen_proto_source=true,paths=source_relative,plugins=grpc"
  @protoc_opts_no_descriptor "package_prefix=NoDescriptor,gen_proto_source=true,paths=source_relative,plugins=grpc"
  # Protos that set (elixirpb.file).module_prefix must be skipped here: that option
  # overrides package_prefix entirely, so the no-descriptor pass would emit modules with
  # the same name as the descriptor pass, causing a compile-time conflict.
  # Add any proto that uses the elixirpb.file module_prefix option to this list.
  @skip_no_descriptor ["custom_prefix_service.proto"]

  defp build_protos(_argv) do
    {reflection, fixtures} =
      "./priv/protos/**/*.proto"
      |> Path.wildcard()
      |> Enum.split_with(&String.contains?(&1, "reflection.proto"))

    # compile reflection protos
    cmds =
      reflection
      |> Enum.map(fn reflection_proto ->
        # protoc now grabs the path to the file and includes it with
        # the message name, which for us leads to bad paths
        i_path =
          reflection_proto
          |> Path.split()
          |> List.delete_at(-1)
          |> Path.join()

        "protoc --elixir_out=#{@protoc_opts}:./lib/proto -I=#{i_path} #{reflection_proto}"
      end)

    # compile test protos — once with descriptors, once without
    fixtures
    |> Enum.flat_map(fn proto ->
      i_path =
        proto
        |> Path.split()
        |> List.delete_at(-1)
        |> Path.join()

      with_descriptors =
        "protoc --elixir_out=#{@protoc_opts}:./test/support/protos -I=#{i_path} -I priv/protos -I deps/protobuf/src #{proto}"

      without_descriptors =
        "protoc --elixir_out=#{@protoc_opts_no_descriptor}:./test/support/protos -I=#{i_path} -I priv/protos -I deps/protobuf/src #{proto}"

      if Enum.any?(@skip_no_descriptor, &String.contains?(proto, &1)) do
        [with_descriptors]
      else
        [with_descriptors, without_descriptors]
      end
    end)
    |> Enum.concat(cmds)
    |> Task.async_stream(&Mix.shell().cmd(&1))
    |> Enum.to_list()
  end

  defp package do
    %{
      name: "grpc_reflection",
      files: ~w(.formatter.exs mix.exs lib),
      links: %{"GitHub" => @source_url},
      licenses: ["Apache-2.0"]
    }
  end

  defp docs do
    [
      extras: [
        "README.md": [title: "Overview"]
      ],
      main: "readme",
      source_url: @source_url,
      source_ref: "#{@version}",
      formatters: ["html"]
    ]
  end

  defp dialyzer do
    [
      plt_local_path: "priv/plts/project",
      plt_core_path: "priv/plts/core",
      plt_add_apps: [:ex_unit],
      list_unused_filters: true
    ]
  end
end