Now that getexecpath(3) is in base we might as well use it.
I should be able to upstream the patch once 8.0 is out.

Here's one tangible improvement, not that this one matters in practice
thanks to the wrappers.

Before:
% cd /usr/local/lib/ghc-9.10.3/bin
% ./ghc-9.10.3 --interactive
Missing file: ./lib/settings

After:
% cd /usr/local/lib/ghc-9.10.3/bin
% ./ghc-9.10.3 --interactive
λ> import System.Environment
λ> getExecutablePath
"/usr/local/lib/ghc-9.10.3/bin/ghc-9.10.3"

 From bd3ab0ae4d8fdc860918ea032f358b8a6eecd81b Mon Sep 17 00:00:00 2001
From: Greg Steuck <[email protected]>
Date: Mon, 21 Sep 2026 01:04:15 +0200
Subject: [PATCH] lang/ghc: use getexecpath(3) for executablePath

ghc-boot uses base executablePath, choosing at run time so the port
still builds with a bootstrap compiler whose base predates
getexecpath(3), and mark openbsd as query-capable in the
executablePath test.
---
 lang/ghc/Makefile                             |  1 +
 .../patch-libraries_ghc-boot_GHC_BaseDir_hs   | 47 +++++++++++++++
 ...rnal_System_Environment_ExecutablePath_hsc | 57 +++++++++++++++++++
 ...testsuite_tests_lib_base_executablePath_hs | 19 +++++++
 4 files changed, 124 insertions(+)
 create mode 100644 lang/ghc/patches/patch-libraries_ghc-boot_GHC_BaseDir_hs
 create mode 100644 
lang/ghc/patches/patch-libraries_ghc-internal_src_GHC_Internal_System_Environment_ExecutablePath_hsc
 create mode 100644 
lang/ghc/patches/patch-testsuite_tests_lib_base_executablePath_hs

diff --git a/lang/ghc/Makefile b/lang/ghc/Makefile
index e780792178d8..f995ea4280f1 100644
--- a/lang/ghc/Makefile
+++ b/lang/ghc/Makefile
@@ -15,6 +15,7 @@ USE_NOBTCFI =         Yes
 
 GHC_VERSION =          9.10.3
 DISTNAME =             ghc-${GHC_VERSION}
+REVISION =             0
 CATEGORIES =           lang devel
 HOMEPAGE =             https://www.haskell.org/ghc/
 
diff --git a/lang/ghc/patches/patch-libraries_ghc-boot_GHC_BaseDir_hs 
b/lang/ghc/patches/patch-libraries_ghc-boot_GHC_BaseDir_hs
new file mode 100644
index 000000000000..a16a3887d9f0
--- /dev/null
+++ b/lang/ghc/patches/patch-libraries_ghc-boot_GHC_BaseDir_hs
@@ -0,0 +1,47 @@
+Let ghc-boot use base's executablePath where base has one, while still building
+with a bootstrap compiler whose base predates getexecpath(3).  See the comment
+in the hunk for why MIN_VERSION_base cannot express this.
+
+Index: libraries/ghc-boot/GHC/BaseDir.hs
+--- libraries/ghc-boot/GHC/BaseDir.hs.orig
++++ libraries/ghc-boot/GHC/BaseDir.hs
+@@ -24,11 +24,8 @@ import Data.List (stripPrefix)
+ import Data.Maybe (listToMaybe)
+ import System.FilePath
+ 
+-#if MIN_VERSION_base(4,17,0) && !defined(openbsd_HOST_OS)
+-import System.Environment (executablePath)
+-#else
+ import System.Environment (getExecutablePath)
+-#endif
++import qualified System.Environment as Base (executablePath)
+ 
+ -- | Expand occurrences of the @$topdir@ interpolation in a string.
+ expandTopDir :: FilePath -> String -> String
+@@ -45,17 +42,16 @@ expandPathVar var value str
+ expandPathVar var value (x:xs) = x : expandPathVar var value xs
+ expandPathVar _ _ [] = []
+ 
+-#if !MIN_VERSION_base(4,17,0) || defined(openbsd_HOST_OS)
+--- Polyfill for base-4.17 executablePath and OpenBSD which doesn't
+--- have executablePath. The best it can do is use argv[0] which is
+--- good enough for most uses of getBaseDir.
++-- ghc-boot is compiled both by the bootstrap compiler and in tree, and only
++-- the in-tree base knows that OpenBSD has a working executablePath: OpenBSD
++-- gained one, via getexecpath(3), without a base version bump, so no
++-- MIN_VERSION_base test can tell the two apart.  Pick at run time instead.
++-- Without the argv[0] fallback a stage0 ghc cannot locate its own topdir and
++-- dies with "missing -B<dir> option" part way through building stage1.
+ executablePath :: Maybe (IO (Maybe FilePath))
+-executablePath = Just (Just <$> getExecutablePath)
+-#elif !MIN_VERSION_base(4,18,0) && defined(js_HOST_ARCH)
+--- executablePath is missing from base < 4.18.0 on js_HOST_ARCH
+-executablePath :: Maybe (IO (Maybe FilePath))
+-executablePath = Nothing
+-#endif
++executablePath = case Base.executablePath of
++  Just query -> Just query
++  Nothing    -> Just (Just <$> getExecutablePath)
+ 
+ -- | Calculate the location of the base dir
+ getBaseDir :: IO (Maybe String)
diff --git 
a/lang/ghc/patches/patch-libraries_ghc-internal_src_GHC_Internal_System_Environment_ExecutablePath_hsc
 
b/lang/ghc/patches/patch-libraries_ghc-internal_src_GHC_Internal_System_Environment_ExecutablePath_hsc
new file mode 100644
index 000000000000..49153b16de24
--- /dev/null
+++ 
b/lang/ghc/patches/patch-libraries_ghc-internal_src_GHC_Internal_System_Environment_ExecutablePath_hsc
@@ -0,0 +1,57 @@
+Implement executablePath on OpenBSD using getexecpath(3), new in OpenBSD 8.0.
+Without it OpenBSD falls through to the argv[0] fallback and executablePath is
+Nothing.
+
+Index: 
libraries/ghc-internal/src/GHC/Internal/System/Environment/ExecutablePath.hsc
+--- 
libraries/ghc-internal/src/GHC/Internal/System/Environment/ExecutablePath.hsc.orig
++++ 
libraries/ghc-internal/src/GHC/Internal/System/Environment/ExecutablePath.hsc
+@@ -82,6 +82,15 @@ import GHC.Internal.System.IO.Error (isDoesNotExistErr
+ import GHC.Internal.System.Posix.Internals
+ #include <sys/types.h>
+ #include <sys/sysctl.h>
++#elif defined(openbsd_HOST_OS)
++import GHC.Internal.Control.Exception (catch, throw)
++import GHC.Internal.Foreign.C.Types
++import GHC.Internal.Foreign.C.Error
++import GHC.Internal.Foreign.C.String
++import GHC.Internal.Foreign.Marshal.Alloc
++import GHC.Internal.System.IO.Error (isDoesNotExistError)
++import GHC.Internal.System.Posix.Internals
++#include <limits.h>
+ #elif defined(mingw32_HOST_OS)
+ import GHC.Internal.Control.Exception
+ import GHC.Internal.Control.Monad.Fail
+@@ -296,6 +305,33 @@ executablePath = Just (fmap Just getExecutablePath `ca
+ 
+   -- As far as I know, ENOENT is the only kind of failure that should be
+   -- expected and handled.  Re-throw other errors.
++      | otherwise             = throw e
++
++
++--------------------------------------------------------------------------------
++-- OpenBSD
++
++#elif defined(openbsd_HOST_OS)
++
++foreign import ccall unsafe "getexecpath"
++  c_getexecpath :: CString -> CSize -> IO CInt
++
++-- getexecpath(3), new in OpenBSD 8.0, returns the pathname recorded by
++-- execve(2), already canonicalized as by realpath(3).  PATH_MAX is the buffer
++-- size the manual recommends, and also bounds the recorded path, so the 
ERANGE
++-- failure cannot happen here.
++getExecutablePath =
++    allocaBytes (#const PATH_MAX) $ \buf -> do
++      throwErrnoIfMinus1_ "getExecutablePath" $
++        c_getexecpath buf (#const PATH_MAX)
++      peekFilePath buf
++
++-- ENOENT means execve(2) could not determine the pathname.  Unlinking the
++-- running executable is not such a case: as on NetBSD, the path captured at
++-- execve(2) time keeps being returned.
++executablePath = Just (fmap Just getExecutablePath `catch` f)
++  where
++  f e | isDoesNotExistError e = pure Nothing
+       | otherwise             = throw e
+ 
+ 
diff --git a/lang/ghc/patches/patch-testsuite_tests_lib_base_executablePath_hs 
b/lang/ghc/patches/patch-testsuite_tests_lib_base_executablePath_hs
new file mode 100644
index 000000000000..471f20b76cb9
--- /dev/null
+++ b/lang/ghc/patches/patch-testsuite_tests_lib_base_executablePath_hs
@@ -0,0 +1,19 @@
+OpenBSD can query the executable path via getexecpath(3), and like NetBSD keeps
+returning the original path after the executable has been unlinked.
+
+Index: testsuite/tests/lib/base/executablePath.hs
+--- testsuite/tests/lib/base/executablePath.hs.orig
++++ testsuite/tests/lib/base/executablePath.hs
+@@ -5,9 +5,9 @@ import System.FilePath ((</>), dropExtension, equalFil
+ import System.Exit (exitSuccess, die)
+ 
+ canQuery, canDelete, canQueryAfterDelete :: [String]
+-canQuery = ["mingw32", "freebsd", "linux", "darwin", "netbsd"]
+-canDelete = ["freebsd", "linux", "darwin", "netbsd"]
+-canQueryAfterDelete = ["netbsd"]
++canQuery = ["mingw32", "freebsd", "linux", "darwin", "netbsd", "openbsd"]
++canDelete = ["freebsd", "linux", "darwin", "netbsd", "openbsd"]
++canQueryAfterDelete = ["netbsd", "openbsd"]
+ 
+ 
+ main :: IO ()

Reply via email to