Require common mixfile from umbrella mixfiles

I have a project consisting of 3 other applications and they share a big part of the project configuration. At some point I decided to move that shared configuration into the root mixfile of the repository and refer to it from the umbrella mixfiles. I did that without a problem in an older project using Code.require_file("mix.exs", "../..") in Elixir 1.6.

However this new project uses Elixir 1.9, and I see an error:

Error while loading project :api at /my-project/apps/my-app
** (Mix) Trying to load MyProject.MixProject from "/my-project/mix.exs" 
but another project with the same name was already defined
at "/my-project/mix.exs"

I figured out that my problem is caused by the line https://github.com/elixir-lang/elixir/commit/b9591267dc87f79178a9eba435d4765ed745f55b#diff-aeaaa0f4f8e4f19af512c290b0b0e237R40 where Code.compile_file/1 was used. I’d think that Code.require_file/1 should be used in that context as the loaded mixfile should be tracked, and I would submit a PR for that change if I was sure that my assumption was 100% correct.

However I suppose I might be incorrect, in which case I would be open to suggestions how to achieve the desired: to allow leaf mixfiles to refer to functions exposed by the root mixfile whether I execute “mix” inside the root or in the leaf project folders.

1 Like

Create mix_utils.exs file that will be shared between all Mixfiles. Without project definition, just functions in module.

1 Like

Yeah I thought about that, but wouldn’t like to clutter the root folder with unnecessary files.

I guess my main question is whether mixfiles are special or just normal exs files and obey the standard rules. Please notice that I’m talking about files, not the special module definition which mix expects to be in them.

They are just plain regular elixir files from that regard.

But, if you cause such a file to be evaluated a second, thrid or even fourth time, then the module will be redefined, and also the side effects caused by use Mix.Project (namely registering the project with mix) will happen again and again.

Yeah, I know that it’s the re-evaluation of the module that causes the error that I see.

I’m asking whether the current mix behaviour is really what it has to be - mixfiles are loaded using Code.compile_file/1 when there is an unfallible way of working with exs files containing module definitions using Code.require_file/2.

Out of curiousity, what kind of configuration settings do the applications share?

Well, here it is

defmodule CommonMixfile do
  def project(config) do
    Keyword.merge(
      [
        build_path: "../../_build",
        config_path: "../../config/config.exs",
        deps_path: "../../deps",
        lockfile: "../../mix.lock",
        elixir: "~> 1.9",
        elixirc_paths: elixirc_paths(Mix.env()),
        elixirc_options: elixirc_options(Mix.env()),
        start_permanent: Mix.env() == :prod
      ],
      config
    )
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

  def elixirc_options(env) do
    case env do
      :test -> []
      _ -> [warnings_as_errors: true]
    end
  end
end

and the usage

Code.require_file("mix.exs", "../..")

defmodule MyApp.MixProject do
  use Mix.Project

  def project do
    CommonMixfile.project(
      app: :my_app,
      version: "0.1.0",
      compilers: [:phoenix, :gettext] ++ Mix.compilers(),
      deps: deps()
    )
  end
1 Like

Ok, so I can see how you merging configuration settings and dependencies, but I’m not sure about the reasoning behind this. I figured the actual settings in config.exs would tell me something about the applications you are integrating and how it would be beneficial to share configs among them.
Maybe you could use environment variables so that each application could access the exact same setting while maintaining their own configs.
I’m curious about the coupling/decoupling of the configs/applications.

I’m not sure what kind of coupling you mean. Here the standard project options are defined in one place which is logically one level higher than every single project. And every project can override them however they need, compilers option is an example.

The idea to have common options appeared once again when I wanted to enforce warnings_as_errors flag across all the projects in the repository.

For example multiple applications that have exactly the same dependencies in common seems unlikely or coincidental. I am wondering what’s the beneficial aspect of grouping all dependencies together in one place at the cost of coupling. (versus having multiple standalone self sufficient applications that have easy to override settings)

Of course they don’t have the same dependencies, the example above shows what they have in common: mix and compiler options. And deps are defined in each project separately. I’m not sure where in the example above you see all the dependencies together in one place.

When applications are developed in an umbrella project, one of them generally depends on the rest (possibility transitively) and that is just code organisation to express boundaries and interfaces between components of the system. An example of that is an application implementing some service, and another application implementing web interface to that service. The former doesn’t need to even know what kind of external interface is provided, the later depends on the former and calls if from plugs/controllers. However they should share the same compiler version, options, mix lockfile and deps directory.

Anyway, all this is quite off the original topic. If you want to discuss the umbrella projects more, let’s create a separate thread for that.

You are right that I am mostly interested in your reasoning about configuring a multi application setup, which is probably off topic as it doesn’t answer your question.
I have been experimenting with setting up applications literally as dependencies (implementing some service) and a dependent web interface to those services (that in some cases have their own repos/datases).
Like I said, just curious about your experiences.