From 8be0124014b14500469c3218f9ec7b7e36651300 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Fri, 7 Aug 2026 09:40:20 +0800 Subject: [PATCH] Probe erlc to decide if wx.hrl is actually usable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- lib/desktop/wx/compile.ex | 14 ++----------- lib/desktop/wx/stub.ex | 44 +++++++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/lib/desktop/wx/compile.ex b/lib/desktop/wx/compile.ex index c17a905..adc3a0d 100644 --- a/lib/desktop/wx/compile.ex +++ b/lib/desktop/wx/compile.ex @@ -42,25 +42,15 @@ defmodule Desktop.Wx.Compile do end def wx_available? do - host_target?() and wx_headers_exist?() + host_target?() and Desktop.WxStub.wx_headers_resolvable?() end defp host_target? do System.get_env("MIX_TARGET") in [nil, "host"] end - defp wx_headers_exist? do - case :code.lib_dir(:wx) do - path when is_list(path) -> - File.exists?(Path.join([List.to_string(path), "include", "wx.hrl"])) - - _ -> - false - end - end - defp constant_defs do - if host_target?() and wx_headers_exist?() do + if wx_available?() do constant_defs_from_erlang() else constant_defs_from_fallback() diff --git a/lib/desktop/wx/stub.ex b/lib/desktop/wx/stub.ex index 1df3923..1260b1f 100644 --- a/lib/desktop/wx/stub.ex +++ b/lib/desktop/wx/stub.ex @@ -2,8 +2,8 @@ defmodule Desktop.WxStub do @moduledoc false # Generates src/desktop_wx.erl before any compiler runs. - # - MIX_TARGET host (or unset) + wx.hrl present → include_lib + ?wx macros - # - android / ios / no wx headers → header-free integer fallbacks + # - MIX_TARGET host (or unset) + wx.hrl resolvable by erlc → include_lib + ?wx macros + # - android / ios / wx.hrl missing / wx.hrl not resolvable → header-free integer fallbacks @constant_names ~w( ID_ANY ID_EXIT DEFAULT_FRAME_STYLE NO_BORDER EXPAND HORIZONTAL VERTICAL @@ -41,7 +41,7 @@ defmodule Desktop.WxStub do File.mkdir_p!(Path.dirname(path)) body = - if host_target?() and wx_headers_exist?() do + if host_target?() and wx_headers_resolvable?() do hrl_body(target) else stub_body(target) @@ -55,13 +55,39 @@ defmodule Desktop.WxStub do System.get_env("MIX_TARGET") in [nil, "host"] end - defp wx_headers_exist? do - case :code.lib_dir(:wx) do - path when is_list(path) -> - File.exists?(Path.join([List.to_string(path), "include", "wx.hrl"])) + # True only when both `wx.hrl` exists on disk AND the host's `erlc` can + # resolve it through the OTP include path. Some hosts (e.g. the + # `erlef/setup-beam` GHA Linux CI used by `diode-drive`) report a path + # via `:code.lib_dir(:wx)` and have `wx.hrl` on disk, but the `erlc` + # that `mix` invokes fails with `can't find include lib "wx/include/wx.hrl"`. + # Probing with a throwaway include is the only reliable way to tell. + def wx_headers_resolvable? do + with lib when is_list(lib) <- :code.lib_dir(:wx), + true <- File.exists?(Path.join([List.to_string(lib), "include", "wx.hrl"])), + {:ok, _} <- probe_include_lib() do + true + else + _ -> false + end + end + + defp probe_include_lib do + src = Path.join(System.tmp_dir!(), "desktop_wx_probe.erl") + out = Path.join(System.tmp_dir!(), "desktop_wx_probe.beam") - _ -> - false + try do + File.write!( + src, + "-module(desktop_wx_probe).\n-include_lib(\"wx/include/wx.hrl\").\n-export([ok/0]).\nok() -> 1.\n" + ) + + case System.cmd("erlc", ["-o", System.tmp_dir!(), src], stderr_to_stdout: true) do + {_out, 0} -> {:ok, :ok} + other -> other + end + after + File.rm(src) + File.rm(out) end end