Working around circular library dependency

Benchee is a wonderful benchmarking tool, and one of the formatters for this tool, benchee_html, has a dependency on Poison to do some JSON encoding for us. However, the maintainer of Poison would like to use Benchee to benchmark his library.

Now, this would be easily solved if we made the dependency on Poison optional in Benchee, but due to some UX worries (mainly users having to put two dependencies in their mix.exs file instead one just benchee), I’d like to avoid that.

Can anyone think of any other way that we can make this circular dependency not an issue to Poison can easily use a library that depends on it? I’ve tried having Poision override the dependency on itself in benchee_html, but that doesn’t seem to work.

3 Likes

On a related note, @OvermindDL1 and I would like to use ExDoc + Makeup (which depends on ExSpirit) to document ExSpirit.

Can’t you install Benchee as an archive?

It seems more like a tool than a dependency of your application / library, so actually having it as a dependency feels a bit odd to me.

Unfortunately that doesn’t work. I get the same error when I do mix archive.install:

** (Mix) Trying to load Poison.Mixfile from "/private/var/folders/95/hllpnf8d5xx39_jxm5nzm1s80000gn/T/mix-local-installer-fetcher-o9CmaA/deps/poison/mix.exs" but another project with the same name was already defined at "/Users/devoncestes/sandbox/poison/mix.exs"

Yeah, cannot use exdoc to document exspirit when exdoc also requires exspirit. @tmbb tried doing something like {:ex_spirit, path: "." ...} and mix just kind of breaks. It really should be possible to have a project itself fulfill a same-named dependency of some dependency on that project, after all it is obviously loaded in. ^.^;

/me understands that recursive compile-time module calls would fail as they would in any other case though, but these are not issues with ex_spirit/ex_doc nor benchee/poison

1 Like

Just a clarification, ex_doc doesn’t require ex_spirit, only the custom syntax highlighting library does (this functionality is not available on hex yet because it depends on some changes to ex_doc that haven’t hit master).

1 Like

We may be able to solve this so I am opening up an issue https://github.com/elixir-lang/elixir/issues/6851.

It would only work if the recursion happens on the current Mix project which seems to be the case for both projects in this thread Poison and ExSpirit. So if you have dependencies that have their own loops it wouldn’t work so I would only suggest doing this for non-prod dependencies. It also wouldn’t work in cases where there are compile time dependencies in the loop.

Yeah, I don’t think it would work on ExSpirit + Makeup because it is a compile time dependency and Mix works on the assumption that all dependencies are compiled before the current project. It might work for Benchee+Poison though.

Is there any reason why the JSON features are part of Benchee and not a Benchee extension? For example, even Poison itself like won’t use the JSON exporting features of Benchee. Or would it?

@josevalim is correct. I didn’t test ExSpirit correctly, the solution I had in mind wouldn’t work in this case. I don’t think there’s any way we can solve circular compile time dependencies.

It is part of an extension and Poison is using that extension.

1 Like

@tmbb You can generate documentation by using the ex_doc executable [1]. Earmark does the same thing [2].

[1] https://github.com/elixir-lang/ex_doc/tree/master/bin
[2] https://github.com/pragdave/earmark/blob/master/tasks/docs.exs

Well ex_spirit would only need it for document generation, that is the only place a circular dependency exists, which is well after all are compiled, so it should work fine there too. :slight_smile:

Except it’s only needed for document generation though? The order would be ex_spirit -> makeup/etc... -> ex_spirit documentation generation.

Dependencies are always compiled before the main project in Mix. Makeup is a dependency of ExSpirit and calls macros from ExSpirit. So there’s no way to solve this compilation order.

Even though all calls from ex_spirit → makeup are purely module calls via ex_doc, no macro’s (I.E. the macro calls are one-way, not circular)?

Makeup is calling exspirit at compile time https://github.com/tmbb/makeup/blob/master/lib/makeup/lexer/common/macros.ex#L3. Exspirit is always compiled after makeup because exspirit is the top-level project.

Ah so the top-level project is force compiled last regardless of if it would fit further down in the dependency tree? Funky… I’m guessing that is a ruby’ism (I’m more used to distinct modules that are calculated file by file instead of project by project in programming languages, like ocaml for example).

No, this is not right, sorry. Let me try to explain:

  1. ex_doc has no bothersome dependencies
  2. ex_doc can be given as a configuration option a module that will act as the markdown processor
  3. I have a package (unreleased) called ex_doc_makeup that depends on ex_doc and makeup. This package contains the module I need to pass to ex_doc as a configuration option.
  4. makeup depends on ex_spirit. This means that ex_doc_makeup also depends on ex_spirit

So ex_spirit does NOT depend on makeup, it’s the other way around.

We want to use ex_doc_makeup to document ex_spirit.

I like the option of the ex_doc binary. Could I define an ex_doc_makeup script of my own in that configures ex_doc the way I want it and run my configured version, as opposed to the version configured by default in the ex_doc package? I’m sorry if the question is confusing or doesn’t make much sense, I’m not very familiar with scripts.

2 Likes

Projects are compiled one at a time. They are compiled in the order the dependencies have been declared by sorting the dependency graph. Since the top-level mix project is at the root of the graph it is compiled last.

They both depend on each other, transitively or directly, otherwise it wouldn’t be a circular dependency. They may not depend on each other in some configurations but in the configuration we are discussing there is a circular dependency.

As long as it still works as a dependency, when I build/test/publish in my CI here it is a clean slate every time, no escripts or so. ^.^

Ah, ugh, that is definitely not standard in the native compiling world… o.O

Well in that case, is there any way to define to load a dependency but not actually ‘depend’ on it as a dependency? That would be a perfect use-case for ex_doc and things like this for example. ^.^

The ex_doc binary has flags you can pass it to configure it’s run, it should probably support flags for configuring the markdown renderer to use.