I have a program which can provide different services, depending on its run-time configuration (through a configuration file or command-line options). Some of the services, but not all, depend on external libraries, such as HTTPoison, so I add in my mix.exs:
{:httpoison, "~> 1.8"}
But I would like to avoid this dependency (HTTPoison brings a lot of other libraries) if not necessary. How to make it conditional, and allow the program to know if it has been included or not, so it can produce a proper error message if the user tries to activate the service, without having the library?
There’s an optional option, though it’s meant more for dependencies within libraries, so the app using the library can choose to include it or not:
{:httpoison, "~> 1.8", optional: true}
But maybe something like this:
def dep_enabled?(env_key), do: not is_nil(System.get_env(env_key))
def deps() do
[{:httpoison, "~> 1.8", optional: dep_enabled?("POISON_IS_GOOD_FOR_YOU")}
...
Actually, re-reading the docs it looks like in your case (if this is a top level app) it would always be included, so maybe a similar approach but using only to specify [:dev, :test] if the dep is disabled, and [:dev, :test, :prod] if it should be enabled?
defmodule SomeModule do
if Mix.target() == :your_target_no do
def some_function do
HTTPoison.get("elixirforum.com")
end
else
def some_function do
# Do something without HTTPoison
end
end
end
Not sure if that suits your needs and how complex the httpoison integration is in your app.
Unfortunately, it does not help since it is at compile-time that Elixir needs to know things such as data structures (see the error message I gave). Function calls may be handled by your trick but not pattern matching with types. I’m afraid there is no easy solution.
Are you wrapping the functions that have pattern matching with conditions checking for the target like in @moogle19’s example? This would be applied at compile time, vs at runtime if your conditions are defined within the function.
For the record, here is a complete example, showing it doesn’t work.
mix.exs:
defmodule ConditionalDependencies.MixProject do
use Mix.Project
def project do
[
app: :conditional_dependencies,
version: "0.1.0",
elixir: "~> 1.9",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:httpoison, "~> 1.8", targets: :http}
]
end
end
conditional_dependencies.ex:
def hello do
# By default, Mix.target() == host
if Mix.target() == :http do
HTTPoison.start()
IO.puts("HTTPoison started")
result = HTTPoison.get("https://elixirforum.com/t/conditional-dependencies-in-mix-exs/48468")
case result do
{:ok, %HTTPoison.Response{status_code: 200} = _data} ->
IO.puts("It worked")
_other ->
IO.puts("HTTP failed")
end
else
IO.puts("No HTTPoison")
end
end
end
ConditionalDependencies.hello
And the results:
% MIX_TARGET=http mix run conditional_dependencies.ex
HTTPoison started
It worked
Calling functions from the Mix module inside of functions at runtime can be tricky - for instance it will fail loudly if running in a release.
In this case, the compiler still has to compilehello’s contents even if the target isn’t set. That gets the error that you’re seeing. An alternative way is to prevent the compiler from seeing the code at all:
if Mix.target() == :http do
def hello do
HTTPoison.start()
IO.puts("HTTPoison started")
result = HTTPoison.get("https://elixirforum.com/t/conditional-dependencies-in-mix-exs/48468")
case result do
{:ok, %HTTPoison.Response{status_code: 200} = _data} ->
IO.puts("It worked")
_other ->
IO.puts("HTTP failed")
end
end
else
def hello do
IO.puts("No HTTPoison")
end
end
Ah I hadn’t seen @al2o3cr and was gonna suggest something similar:
defmodule ConditionalDependencies do
if Code.ensure_loaded?(HTTPoison) do
def hello do
HTTPoison.start()
IO.puts("HTTPoison started")
case HTTPoison.get("https://elixirforum.com/t/conditional-dependencies-in-mix-exs/48468") do
{:ok, %HTTPoison.Response{status_code: 200} = _data} ->
IO.puts("It worked")
_other ->
IO.puts("HTTP failed")
end
end
else
def hello do
IO.puts("No HTTPoison")
end
end
end
ConditionalDependencies.hello
I still have a small problem with Dialyzer which complains that HTTP code cannot be reached when I test without HTTP support (and vice-versa) but I can deal with it later.