Fresh new 1.7 Phoenix app inside umbrella fails build

Background

I have a fresh umbrella app and I am trying to create a Phoenix app inside it. However, even though I can create the Phoenix app inside the umbrella, I cannot build it successfully.

Problem

I am creating a phoenix app inside a fresh umbrella project like this:

  1. mix new test_app --umbrella
  2. cd apps
  3. mix phx.new web_interface --no-dashboard --no-ecto --no-gettext --no-mailer --live

These commands run fine. However when I try to setup the app, something breaks:

  1. cd web_interface
  2. mix setup returns:

08:14:50.318 [warning] tailwind version is not configured. Please set it in your config files:

config :tailwind, :version, ā€œ3.2.4ā€

Browserslist: caniuse-lite is outdated. Please run:
npx update-browserslist-db@latest
Why you should do it regularly: GitHub - browserslist/update-db: CLI tool to update caniuse-lite to refresh target browsers from Browserslist config

Rebuildingā€¦

To me, this is odd. Everything is fresh new, so I was really not expecting any warning. But it gets worse. Once rebuilding is done, I get an actual error:

Done in 200ms.

08:14:52.146 [warning] esbuild version is not configured. Please set it in your config files:

config :esbuild, :version, "0.16.4"

** (RuntimeError) no arguments passed to esbuild
(esbuild 0.7.0) lib/esbuild.ex:170: Esbuild.run/2
(esbuild 0.7.0) lib/mix/tasks/esbuild.ex:49: Mix.Tasks.Esbuild.install_and_run/1
(mix 1.14.1) lib/mix/task.ex:421: anonymous fn/3 in Mix.Task.run_task/4
(mix 1.14.1) lib/mix/task.ex:479: Mix.Task.run_alias/6
(mix 1.14.1) lib/mix/cli.ex:84: Mix.CLI.run_task/2

At first I thought this was happening because I had something outdated in my machine. So I did some updates:

  1. mix local.hex
  2. mix archive.install hex phx_new

However, this did not fix the issue.

I am running on Windows.

Questions

  1. It is my understanding that if I create a new app inside an umbrella project, elixir is smart enough to detect that and I donā€™t need to do anything new/different. Is this the case?
  2. IIRC the --live flag became a default in Phoenix 1.6. Is this still case for 1.7? (Meaning all apps are live now, by default?)
  3. Why am I getting this error? How can I fix it?

I guess it is a problem of configurationā€¦

Umbrellas have their config in the root folder, but if You go to apps/any to start the UI, then You also need config in apps/any/config.

I would try to start from the root folder, or check config files, in both location

If I go to test_app instead of test_app/apps/web_interface and run iex -S mix phx.server I get the similar warnings:

Ī» iex -S mix phx.server
==> web_interface
Compiling 12 files (.ex)
warning: redefining module WebInterfaceWeb.ErrorHTML (current version loaded from c:/test_app/_build/dev/lib/web_interface/ebin/Elixir.WebInterfaceWeb.ErrorHTML.beam)
  lib/web_interface_web/controllers/error_html.ex:1

warning: redefining module WebInterfaceWeb (current version loaded from c:/test_app/_build/dev/lib/web_interface/ebin/Elixir.WebInterfaceWeb.beam)
  lib/web_interface_web.ex:1

warning: redefining module WebInterface (current version loaded from c:/test_app/_build/dev/lib/web_interface/ebin/Elixir.WebInterface.beam)
  lib/web_interface.ex:1

warning: redefining module WebInterfaceWeb.CoreComponents (current version loaded from c:/test_app/_build/dev/lib/web_interface/ebin/Elixir.WebInterfaceWeb.CoreComponents.beam)
  lib/web_interface_web/components/core_components.ex:1

warning: redefining module WebInterfaceWeb.Layouts (current version loaded from c:/test_app/_build/dev/lib/web_interface/ebin/Elixir.WebInterfaceWeb.Layouts.beam)
  lib/web_interface_web/components/layouts.ex:1

warning: redefining module WebInterface.Application (current version loaded from c:/test_app/_build/dev/lib/web_interface/ebin/Elixir.WebInterface.Application.beam)
  lib/web_interface/application.ex:1

warning: redefining module WebInterfaceWeb.ErrorJSON (current version loaded from c:/test_app/_build/dev/lib/web_interface/ebin/Elixir.WebInterfaceWeb.ErrorJSON.beam)
  lib/web_interface_web/controllers/error_json.ex:1

warning: redefining module WebInterfaceWeb.PageHTML (current version loaded from c:/test_app/_build/dev/lib/web_interface/ebin/Elixir.WebInterfaceWeb.PageHTML.beam)
  lib/web_interface_web/controllers/page_html.ex:1

warning: redefining module WebInterfaceWeb.PageController (current version loaded from c:/test_app/_build/dev/lib/web_interface/ebin/Elixir.WebInterfaceWeb.PageController.beam)
  lib/web_interface_web/controllers/page_controller.ex:1

warning: redefining module WebInterfaceWeb.Endpoint (current version loaded from c:/test_app/_build/dev/lib/web_interface/ebin/Elixir.WebInterfaceWeb.Endpoint.beam)
  lib/web_interface_web/endpoint.ex:1

Generated web_interface app

09:08:09.989 [warning] esbuild version is not configured. Please set it in your config files:

    config :esbuild, :version, "0.16.4"


09:08:10.031 [warning] tailwind version is not configured. Please set it in your config files:

    config :tailwind, :version, "3.2.4"


09:08:10.777 [warning] no configuration found for otp_app :web_interface and module WebInterfaceWeb.Endpoint

Running the mentioned command in root (test_app) redefined a bunch of modules (like we see above) but didnā€™t really do anything usefull.

I would hope the default configuration would simply allow me to create a project and quickly run it.
It is rather frustrating this is not the case.

I am starting to think Phoenix does not really have umbrella apps in mind and was never meant to play nice with them.

Configs

test_app/config/config.exs:

# This file is responsible for configuring your umbrella
# and **all applications** and their dependencies with the
# help of the Config module.
#
# Note that all applications in your umbrella share the
# same configuration and dependencies, which is why they
# all use the same configuration file. If you want different
# configurations or dependencies per app, it is best to
# move said applications out of the umbrella.
import Config

test/apps/web_interface/config/config.exs:

# This file is responsible for configuring your application
# and its dependencies with the aid of the Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.

# General application configuration
import Config

# Configures the endpoint
config :web_interface, WebInterfaceWeb.Endpoint,
  url: [host: "localhost"],
  render_errors: [
    formats: [html: WebInterfaceWeb.ErrorHTML, json: WebInterfaceWeb.ErrorJSON],
    layout: false
  ],
  pubsub_server: WebInterface.PubSub,
  live_view: [signing_salt: "9VQi7/cB"]

# Configure esbuild (the version is required)
config :esbuild,
  version: "0.17.11",
  default: [
    args:
      ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

# Configure tailwind (the version is required)
config :tailwind,
  version: "3.2.7",
  default: [
    args: ~w(
      --config=tailwind.config.js
      --input=css/app.css
      --output=../priv/static/assets/app.css
    ),
    cd: Path.expand("../assets", __DIR__)
  ]

# Configures Elixir's Logger
config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id]

# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{config_env()}.exs"

It does not answer your question, but I would use umbrellas only when there should be a clear database separation between each element. There is also the advantage of colocating configuration for all pieces.

Using no-ectoā€¦ I think You might as well create a normal project
Using --live is not needed anymore, it is the default

As said, it wonā€™t answer your question, just some thoughts about your setup. Mine is usually to use plugins instead of umbrellas

Umbrella apps used to store config in per-app directories, but it was intensely confusing because the top-level would load them all, so configuring ā€œapp Aā€ and ā€œapp Bā€ differently wasnā€™t actually possible even though it looked like it might work.

If you add the lines the error messages are asking about to your main config/ files, does the app boot?

An opinion I respect, but disagree with. I am more of a fan of Dave Thomasā€™s line of thinking here, hence why I went from an umbrella app.

Since the first iterations, the project I am doing has always been an umbrella project and has always had a web_interface child app. This has, in all honesty, been very grueling. I had to remake the entire web_interface once, and now with this new update, I have to do the same again.

I am considering dismantling my umbrella app into separate libraries, that use each other. But I want to give this one last try. Every time I have an issue, I have this feeling that umbrella projects were created, but then were left adrift in the wind and very few people actually use them.

Can you elaborate? I absolutely need Phoenix for this. I need to design a web interface in Elixir.
DB is not a concern here. That is part of the backend.

Great, one question answered !

I have copy pasted the contents of apps/web_interface/config/config.exs into config/config.exs. The resulting file is:

# This file is responsible for configuring your umbrella
# and **all applications** and their dependencies with the
# help of the Config module.
#
# Note that all applications in your umbrella share the
# same configuration and dependencies, which is why they
# all use the same configuration file. If you want different
# configurations or dependencies per app, it is best to
# move said applications out of the umbrella.
import Config

# Configures the endpoint
config :web_interface, WebInterfaceWeb.Endpoint,
  url: [host: "localhost"],
  render_errors: [
    formats: [html: WebInterfaceWeb.ErrorHTML, json: WebInterfaceWeb.ErrorJSON],
    layout: false
  ],
  pubsub_server: WebInterface.PubSub,
  live_view: [signing_salt: "9VQi7/cB"]

# Configure esbuild (the version is required)
config :esbuild,
  version: "0.17.11",
  default: [
    args:
      ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

# Configure tailwind (the version is required)
config :tailwind,
  version: "3.2.7",
  default: [
    args: ~w(
      --config=tailwind.config.js
      --input=css/app.css
      --output=../priv/static/assets/app.css
    ),
    cd: Path.expand("../assets", __DIR__)
  ]

# Configures Elixir's Logger
config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id]

# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason

Nothing out of the ordinary here.
Now once I run mix setup things get interesting once again:

Ī» mix setup
Resolving Hex dependenciesā€¦
Resolution completed in 0.083s
Unchanged:
ā€¦
All dependencies are up to date

13:42:04.155 [debug] Downloading tailwind from https://github.com/tailwindlabs/tailwindcss/releases/download/v3.2.7/tailwindcss-windows-x64.exe

13:42:06.879 [debug] Downloading esbuild from https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.11.tgz
** (ErlangError) Erlang error: :eacces
erlang.erl:4481: :erlang.open_port({:spawn_executable, ā€˜c:/test_app/_build/tailwind-windows-x64.exeā€™}, [:stderr_to_stdout, {:env, }, {:cd, ā€œc:/test_app/assetsā€}, :use_stdio, :exit_status, :binary, :hide, {:args, [ā€œā€“config=tailwind.config.jsā€, ā€œā€“input=css/app.cssā€, ā€œā€“output=ā€¦/priv/static/assets/app.cssā€]}])
(elixir 1.14.1) lib/system.ex:1071: System.do_cmd/3
(tailwind 0.2.0) lib/tailwind.ex:185: Tailwind.run/2
(tailwind 0.2.0) lib/mix/tasks/tailwind.ex:51: Mix.Tasks.Tailwind.install_and_run/1
(mix 1.14.1) lib/mix/task.ex:421: anonymous fn/3 in Mix.Task.run_task/4
(mix 1.14.1) lib/mix/task.ex:479: Mix.Task.run_alias/6
(mix 1.14.1) lib/mix/cli.ex:84: Mix.CLI.run_task/2

I am running this on a Windows machine. I have found both tailwind-windows-x64.exe and esbuild-win32-x64 in the respective folder. However, when I click the .exe file. nothing happens. I wonder if something is crashing because there is an issue with Windows.

Peek 2023-05-18 22-47

This is possibly a long shot, but I noticed the mix command you used to install the web_interface was: mix phx.new web_interface ....

You should use mix phx.new.web, mix phx.new.ecto, or mix new when installing a new app in the apps directory of an umbrella project.

When using mix phx.new.web a config/ directory will not be created in the root of the new app. It will be set up to share config directory at the root of the umbrella project.

Could that be the issue?

2 Likes

This was the issue. To me, this is rather confusing, as from the documentation/tutorials (https://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-projects.html#umbrella-projects) we have this quote:

First of all, since we generated this project inside kv_umbrella/apps, Mix automatically detected the umbrella structure (ā€¦)

Which states that when creating an app inside an umbrella project, Mix does have the capacity to detect it and act accordingly.

This does not hold true for Phoenix apps. For reasons beyond my understanding, Phoenix apps do not know their context and thus the user needs to tell mix that the new app will be inside an umbrella project.

This is confusing to me. Sometimes mix is smart enough to know, other times, it isnā€™t. I donā€™t know which times it knows and which times it doesnā€™t.

So, to conclude, given the original questions:

  1. Wrong. Mix detects umbrella projects when creating normal apps. When creating Phoenix apps, it detects nothing. The user must use mix phx.new.web. It is unclear when Mix is able to detect umbrella apps and when its not.
  2. --live flag is now default and not needed.
  3. Because I was creating the Phoenix app with an incorrect assumption and thus using the incorrect command.