View Source Converting Umbrella Project

Umbrella projects dependencies are marked with in_umbrella and share a common configuration (like build_path, deps_path, ...) in mix.exs.

One of the pain points with umbrella projects is that compilation is ran for each app irregardless of any changes having occurred. If there are slow compilation tasks or there are many applications compilation will start to take a significant amount of time. With short iterations and frequent compilation this may become a annoyance, or expensive in case of CI pipelines.

To convert a umbrella project can be done with a simple switch:

defmodule UmbrellaProject.MixProject do
  use Mix.Project

  def project do
    [
      apps_path: "apps",
      version: "0.1.0",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      aliases: aliases(),
      releases: releases(),
      elixirc_options: [
        warnings_as_errors: true
      ]
    ]
  end

  # removed for brievety
end

First is to add ExMonorepo to the project, remove apps_path and add a app key. This will make one application with the umbrella apps as dependencies.

@@ -1,9 +1,9 @@
 defmodule Umbrella.MixProject do
   use Mix.Project
+  use ExMonorepo
 
   def project do
     [
-      apps_path: "apps",
+      app: :umbrella_parent,
       version: "0.1.0",
       start_permanent: Mix.env() == :prod,
       deps: deps(),

Then make a new file monorepo.exs and set the repo variable:

echo 'repo = "./apps"' > monorepo.exs

Running mix deps.tree will most likely be empty, add a app as dependencies:

user@nyx:~/src/fwt/exmonorepo/tnest-edge$ g diff mix.exs 
diff --git a/mix.exs b/mix.exs
index 8201047c..5bd2cde0 100644
--- a/mix.exs
+++ b/mix.exs
@@ -60,7 +61,8 @@ defmodule Umbrella.MixProject do
       {:credo, "~> 1.5", only: [:dev, :test], runtime: false},
       {:junit_formatter, "~> 3.3", only: [:test]},
       {:dialyzex, "~> 1.2.0", only: :dev},
+      {:umbrella_web, "~> 0.1"},
     ]
   end

Running mix deps.tree should now show a longer list:

$ mix deps.tree
umbrella_parent
├── dialyzex ~> 1.2.0 (Hex package)
├── dispatchex ~> 0.1 (Hex package)
├── junit_formatter ~> 3.3 (Hex package)
├── umbrella ~> 0.1 (repo: umbrella)
├── umbrella_web ~> 0.1 (repo: umbrella_web)
│   ├── esbuild ~> 0.4 (Hex package)
│   │   └── castore >= 0.0.0 (Hex package)
│   ├── ex_cldr_dates_times ~> 2.12 (Hex package)
│   │   └── ...
│   ├── gettext ~> 0.18 (Hex package)
│   │   └── expo ~> 0.4.0 (Hex package)
│   ├── jason ~> 1.0 (Hex package)
│   ├── phoenix ~> 1.7.0 (Hex package) *override*
│   │   └── ...
│   ├── tailwind ~> 0.1 (Hex package)
│   │   └── castore >= 0.0.0 (Hex package)
└── toml ~> 0.6.1 (Hex package)

This detects all the apps and should compile and work out of the box. You might need to delete _build or deps. Depending on the structure of your application you may need to add them in deps/0 in the project.

Performance

Testing recompilation time of converted umbrella project with 10 apps gives the same recompilation time as the original umbrella. It's assumed this is due to each app being recompiled in sequence. There's no significant improvement as of exmonorepo-v0.2.1. For scenarios it may be faster if it's naive approach to change detection avoids recompiling several apps.