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 ()
