Skip to content

Make :wx an optional host extra_application - #80

Merged
dominicletz merged 1 commit into
mainfrom
conditional-wx-extra-applications
Aug 6, 2026
Merged

Make :wx an optional host extra_application#80
dominicletz merged 1 commit into
mainfrom
conditional-wx-extra-applications

Conversation

@dominicletz

@dominicletz dominicletz commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Problem

On hosts whose Erlang/OTP was configured --without-wx, mix release (and the higher-level mix desktop.installer) aborts with:

** (Mix) Could not find application :wx

We hit this in elixir-desktop/diode-drive's macOS installer workflow when we dropped the custom wxWidgets build and switched the WebView backend to elixir-desktop/desktop_webview. The OTP installed on the builder no longer provides the :wx OTP application, but desktop's extra_applications(:host) still declares [:wx], so every release validation step fails before desktop_wx_stub.exs has any chance to regenerate src/desktop_wx.erl.

Fix

Only declare :wx in extra_applications(:host) when :code.lib_dir(:wx) actually returns a chardir. That is the same OTP presence probe desktop_wx_stub.exs already uses to decide whether to emit the header-free stub Erlang source vs. the wx.hrl-including real source. A host build on OTP built --without-wx now compiles the stub backend; a host build on OTP that ships :wx keeps the existing behavior.

Diff

   def extra_applications(:host) do
-    [:wx]
+    if :code.lib_dir(:wx) |> is_list() do
+      [:wx]
+    else
+      []
+    end
   end

Verification

  • mix compile --warnings-as-errors clean on OTP that ships :wx.
  • :code.lib_dir(:wx) |> is_list() returns true on standard OTP and false on OTP built --without-wx (returns {:error, :bad_name}, which is not a list).

Why not also remove :wx from mix.exs entirely

Desktop.Window.{Macos,Windows,Linux} and Desktop.Webview.wx still real-call into :wx / wxWidgets when available, and the project still recommends mix desktop.installer workflows on Linux and Windows where wx is expected to be present. We only want to stop requiring it on platforms where the installer drops wx support.

Out of scope

The macOS-side change that motivated this PR (the WebView switch + dropping the wxWidgets build in binaries_macos.yml) lives in elixir-desktop/diode-drive, not here.

Only declare `:wx` in `extra_applications(:host)` when the Erlang/OTP
build that will load the release actually contains the `:wx` OTP
application. Without this guard, `mix release` aborts with
`Could not find application :wx` on hosts where OTP was configured
`--without-wx` (e.g. our macOS installer CI workflow, which switched
to `--without-wx` together with switching to `elixir-desktop/desktop_webview`
for the WebView backend).

The Erlang source `src/desktop_wx.erl` already adapts to missing wx
headers via `desktop_wx_stub.exs`, so a host build without `:wx` simply
compiles the header-free stub backend. No behavioral change for
existing users on OTP builds that ship `:wx`.

Tested locally:
- `mix compile --warnings-as-errors` passes on OTP that ships `:wx`
- `:code.lib_dir(:wx) |> is_list()` returns true on standard OTP
- it returns false on OTP built `--without-wx` (gives
  `{:error, :bad_name}`, which is not a list)

Co-authored-by: Cursor <[email protected]>
@dominicletz
dominicletz merged commit 68455c1 into main Aug 6, 2026
2 checks passed
@dominicletz
dominicletz deleted the conditional-wx-extra-applications branch August 6, 2026 13:49
dominicletz added a commit that referenced this pull request Aug 7, 2026
On some host installs (e.g. the erlef/setup-beam GHA Linux CI used by
diode-drive), :code.lib_dir(:wx) reports a valid path AND wx.hrl exists
on disk, yet the erlc that mix invokes fails with:

  can't find include lib "wx/include/wx.hrl"

This is the root cause of diode-drive's Linux CI regressions after
PR #80. The previous wx_headers_exist?/0 check (File.exists? on the
reported lib path) was not strict enough — the file is present yet
erlc's include_lib search path does not see it.

Replace it with wx_headers_resolvable?/0, which actually invokes erlc
against a throwaway module that includes wx.hrl. The probe is the only
way to mirror the real compilation step that mix will run. When the
probe fails we fall through to the integer-fallback stub, which is
sufficient because every host wx call site is now guarded by
Code.ensure_loaded?(:wx).

The probe uses a stable module/filename pair so erlc's module-name
check does not reject the throwaway source.

Co-authored-by: Cursor <[email protected]>
dominicletz added a commit that referenced this pull request Aug 7, 2026
On some host installs (e.g. the erlef/setup-beam GHA Linux CI used by
diode-drive), :code.lib_dir(:wx) reports a valid path AND wx.hrl exists
on disk, yet the erlc that mix invokes fails with:

  can't find include lib "wx/include/wx.hrl"

This is the root cause of diode-drive's Linux CI regressions after
PR #80. The previous wx_headers_exist?/0 check (File.exists? on the
reported lib path) was not strict enough — the file is present yet
erlc's include_lib search path does not see it.

Replace it with wx_headers_resolvable?/0, which actually invokes erlc
against a throwaway module that includes wx.hrl. The probe is the only
way to mirror the real compilation step that mix will run. When the
probe fails we fall through to the integer-fallback stub, which is
sufficient because every host wx call site is now guarded by
Code.ensure_loaded?(:wx).

The probe uses a stable module/filename pair so erlc's module-name
check does not reject the throwaway source.

Co-authored-by: Cursor <[email protected]>
dominicletz added a commit that referenced this pull request Aug 8, 2026
…#82)

After PR #80 the `extra_applications(:host)` check uses
`:code.lib_dir(:wx)` to decide whether to include `:wx` in the
generated `desktop.app`. That call consults Erlang's `NameDb` ETS
table, which only knows about apps that have been loaded into the
running VM.

`mix deps.compile` runs each dep's `compile.all` in sequence, and each
`compile.all` prunes the code path to only the apps the dep declared
(see `Code.delete_paths(current_paths -- loaded_paths)` in
`Mix.Tasks.Compile.All`). After the first dep that does not list `:wx`
in its apps finishes, `:wx` is removed from both the code path AND
from `NameDb`. By the time `desktop.application/0` is evaluated for
the `:desktop` dep itself, `code:lib_dir(:wx)` returns
`{:error, :bad_name}`, so `:wx` is dropped from `desktop.app` even
though the wx app is still on disk and `wx.hrl` is still resolvable.

The concrete failure window this opens:

1. `ensure_desktop_wx_erl!/0` runs while `NameDb` is still populated
   (before `compile.all` prunes), so `wx_headers_resolvable?/0` is
   true and `src/desktop_wx.erl` is generated with
   `-include_lib("wx/include/wx.hrl")`.
2. `extra_applications(:host)` runs after pruning, so `:wx` is not
   added to the app file.
3. `compile.erlang` then fails with
   `can't find include lib "wx/include/wx.hrl"`.

(Side note: `mix deps.compile` passes `--no-code-path-pruning` to
disable pruning, but `Mix.Tasks.Compile.All` only honors
`--no-prune-code-paths` — different strings — so pruning still
happens regardless of the intent.)

Fix: probe the filesystem directly, mirroring what
`wx_headers_resolvable?/0` already does. The filesystem state is
independent of `NameDb` and stays consistent across the build.

Tested locally on diode-drive: a fresh `mix deps.compile` previously
failed with `can't find include lib "wx/include/wx.hrl"`; after this
patch it succeeds and `_build/dev/lib/desktop/ebin/desktop.app` lists
`:wx` in its `applications` entry. The behavior on hosts without
`:wx` is preserved — `wx_app_on_disk?/0` returns false and the
stub backend is used.

Co-authored-by: Cursor <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant