Proposal: "Dependency Groups" or "one of these dependencies is required, w/ a default"

82 views
Skip to first unread message

Zach Daniel

unread,
Aug 13, 2021, 2:39:17 PM8/13/21
to elixir-lang-core
I originally opened a github issue on hex, but it was pointed out (correctly) that this is actually a `mix` proposal not a `hex` proposal.  https://github.com/hexpm/hex/issues/899

Original Proposal Text:

# The problem

Ash Framework comes with a dependency called [picosat_elixir](https://github.com/bitwalker/picosat_elixir), which uses a NIF. This causes problems for some users, so I've implemented a (slower) alternative natively in Elixir using [csp](https://github.com/Lakret/csp). However, I don't really want most people using the `csp` dependency unless they encounter problems with the `picosat_elixir` dependency. So I'm looking for behavior that is essentially "one of these dependencies needs to be specified, but if none of them are, use `picosat_elixir` as the default". This seems like it could be useful for other use cases, like `json_formatter` or `http_library`, allowing for a more seamless initial installation process, and automatically choosing the recommended library.


# The temporary hacky workaround
I haven't actually released this yet, but in Ash we'd have:

```elixir
{:picosat_elixir, "~> 0.1.5"},
{:csp, "~> 0.1.0", optional: true},
```

And then there is a troubleshooting guide that instructs people who really can't get picosat_elixir set up to add the following to their dependencies:

```elixir
{:picosat_elixir, override: true, only: []},
{:csp, "~> 0.1.0"},
```

Less than ideal :D

# The proposal: Optional Dependency Groups

This is just one potential way it could be implemented:

```elixir
defp deps do
  {:picosat_elixir, "~> 0.1.5", group_default: true, group: "Sat Solver"},
  {:csp, "~> 0.1.0", group: "Sat Solver"}
end
```

We could also use `priority`, e.g `group_priority: 1`, `group_priority: 2`, and then choose the highest priority dependency that has no conflicts.

And then of course if one of the dependencies is explicitly configured in the user's `deps` then we just use that one no matter what.

The benefits of a strategy like this:

Right now, the typical way of switching on dependencies is something like:

```elixir
if Code.ensure_loaded?(ModuleInDependency) do
  ...
end
```

That is theoretically brittle depending on what module you choose. Some other dependency they use could also (very unlikely, but possible) define a module of the same name. But since mix has the group names, we could do something like this:

```elixir
if Mix.group_choice(:ash, "Sat Solver") == :picosat_elixir do
  ....
end
```

I'm sure there are probably a few aspects of this I haven't considered, but it seems like it could be useful for others as well.

We could also display the group something was installed for/the reason of it when installing, e.g

```elixir
csp 0.1.0 (Group: "Sat Solver")
```
Images of the comments attached:

CleanShot 2021-08-13 at 14.39.04@2x.png
CleanShot 2021-08-13 at 14.38.22@2x.png

José Valim

unread,
Aug 13, 2021, 3:08:07 PM8/13/21
to elixir-l...@googlegroups.com
From an initial glance, I don't think this needs to be pushed to the tooling.

I would create an application that sits in the middle and works as an adapter that knows how to interface with both libraries, and the user can configure which one they choose. Both libraries are optional dependencies but the adapter can ensure at least one is available. Not much different than how Ecto SQL can work with multiple or zero libraries.

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/2595654f-86ce-4524-9233-f0bf4614561en%40googlegroups.com.

Yordis Prieto

unread,
Aug 14, 2021, 3:09:17 AM8/14/21
to elixir-lang-core
I was about to create another thread kind of related to this, please my apologies if I misunderstood.

Recently I started working on a package, and I wanted to share as part of the package a lot of TestSupport codes that people would take advantage of in testing. So the dilemma I am having is where to put that TestSupport files:

A. Create a new package called `myname_test_support` just so people could add the package as `only: [:test]` dependency? This is ideally the cleanest way but at a high cost of maintains.
B. Add the test support files as part of `myname` and document that people shouldn't use the modules in production? This is the easiest way but we bundle things in to production code that we don't want, or in the worst case, some programmers don't read the manual and use it as normal production code.
C. Trying to do some sort of dead-code elimination doing like `if Mix.group_choice(:mode, "test") == :test do  .... end` wrapping the entire code in that module? Maybe this is what may be related to this topic, I can't tell. Also, I wouldn't like to bundle those stuff into prod btw, not sure if things like that in your proposal would still do it.

Wojtek Mach

unread,
Aug 14, 2021, 3:25:25 AM8/14/21
to elixir-lang-core
On August 14, 2021, "gmail.com" <yordis...@gmail.com> wrote:
I was about to create another thread kind of related to this, please my apologies if I misunderstood.

Recently I started working on a package, and I wanted to share as part of the package a lot of TestSupport codes that people would take advantage of in testing. So the dilemma I am having is where to put that TestSupport files:

This is something that ecto_sql does too. :) See https://github.com/elixir-ecto/ecto_sql/blob/v3.6.2/integration_test/pg/all_test.exs#L1:L2. Notice we _require_ the files to compile them, they are not compiled because they are not in the `elixirc_path` by default. You just need to remember to include integration test stuff in your package: https://github.com/elixir-ecto/ecto/blob/v3.6.0/mix.exs#L49

Yordis Prieto

unread,
Aug 14, 2021, 5:50:38 PM8/14/21
to elixir-lang-core
For sure, I knew about it because I learned from there actually. I love to have a more declarative way to express the intention, I feel we could embrace sharing TestSupport files more often if it was easy enough to do so. You got the idea, not sure what the direction should be, I trust you on that one.

Christopher Keele

unread,
Aug 16, 2021, 4:55:18 PM8/16/21
to elixir-lang-core
The current idiom for this that José mentions is, in my opinion, superior to the proposed approach.

It is still declarative, but at the code level rather than a data-driven config level within mix, which makes it much more flexible. Ie, it lets more complicated checks occur, can reference application config for values, can report library-specific warnings/error messages rather than generic mix-level ones.

> I would create an application that sits in the middle and works as an adapter that knows how to interface with both libraries

I've seen an even simpler single-file single-module adapter implementation for this that I quite like; something like this.



Yordis Prieto

unread,
Aug 21, 2021, 2:31:48 PM8/21/21
to elixir-lang-core
It seems that Broadway decided to include "Testing things" (please correct me here because that is my interpretation and assumption) https://github.com/dashbitco/broadway/blob/1a628df28a246a0a94101566c9fa5d047974388f/lib/broadway.ex#L1047 that will be useful only in the test environment.

The ideal scenario is that we figure out how to reflect such intention without having to lean on understanding and hidden intention type of thing.

What would be an answer for that situation?
Reply all
Reply to author
Forward
0 new messages