haskell / ghcup-hs

https://www.haskell.org/ghcup/
GNU Lesser General Public License v3.0
290 stars 90 forks source link

Add OpenBSD and NetBSD? #707

Open hasufell opened 1 year ago

hasufell commented 1 year ago

https://github.com/haskell/unix/pull/262

But we have no bindists so far.

habibalamin commented 1 month ago

I've been messing around with GHCup on OpenBSD (7.5), and these are the roadblocks to getting GHCup to compile and run:

hasufell commented 1 month ago

This is great work.

A few comments:

switch to lzma

I don't really trust OpenBSD/NetBSD to care about binary compatibility. So I want to have as few C libraries linked as possible. We can't really fix ghcup linkage and distribute it, because ghcup upgrade would simply fail.

But it could be a stop-gap.

we pass +static as a build flag constraint to lzma in some environments, but it doesn't seem to do anything with it, not even do a static build, which is what I expected; if you can explain why we pass it that build flag, I'd appreciate it).

That would be odd, since the build would then fail on windows.

It's per platform:

if os(linux)
  if arch(x86_64) || arch(i386)
    package *
      ghc-options: -split-sections -optl-static
elif os(darwin)
  constraints: zlib +bundled-c-zlib,
               lzma +static
elif os(mingw32)
  constraints: zlib +bundled-c-zlib,
               lzma +static,
               text -simdutf,
               vty-windows >=0.2.0.2
  if impl(ghc >= 9.4)
    constraints: language-c >= 0.9.3
elif os(freebsd)
  constraints: zlib +bundled-c-zlib,
               zip +disable-zstd
  package *
    ghc-options: -split-sections

libarchive has a transitive dependency on lzma-static, so the above solution alone doesn't entirely solve the problem; you'd also have to enable the tar build flag to use zip and tar dependencies instead of libarchive.

I'm ok switching to tar for some platforms. It has most bugs fixed that caused me to migrate to libarchive.


As for lzma-static: I'd be fine to switch to autoconf based build. I've done that for libarchive too, but it's some maintenance overhead to e.g. clean up the configure.ac and make adjustments.

habibalamin commented 1 month ago

we pass +static as a build flag constraint to lzma in some environments, but it doesn't seem to do anything with it, not even do a static build, which is what I expected; if you can explain why we pass it that build flag, I'd appreciate it).

That would be odd, since the build would then fail on windows.

It's per platform:

To clarify: macOS doesn't support static linking, too, so I was confused, but I just assumed, “if it's not a build flag that configures whether to compile a vendored liblzma like lzma-static does, then it's maybe a flag that Cabal understands by default that configures whether to use static linking?”,

but I couldn't find any Cabal documentation that said that, and I knew macOS, which we enable the flag on, doesn't support static linking, so that's how I discounted that possibility.

I'm ok switching to tar for some platforms. It has most bugs fixed that caused me to migrate to libarchive.

What kind of bugs are we talking about? If they're relevant for GHCup (cause specific issues/breakages under some conditions for GHCup itself), we should link to them from our own issues, so people can tackle them.

As for lzma-static: I'd be fine to switch to autoconf based build. I've done that for libarchive too, but it's some maintenance overhead to e.g. clean up the configure.ac and make adjustments.

Adjustments are already being made for Darwin's config.h in gen-header.sh (in lzma-static). Since the output of gen-header.sh is committed anyway, that part can be removed, but if we switch to an Autoconf-based build, we can remove gen-header.sh entirely,

but keep that portion of the script which seds whatever it needs to in the resulting config.h header file for whatever platforms it needs to do it for (or just fix whatever files need to be fixed pre-configure step so that the config.h header file just comes out correctly on Darwin without the need to sed it).

Obviously, the ideal solution is to fix the issue upstream in xz. If their configure script for liblzma is producing incorrect/less-optimal header files for some platforms, that should be fixed. This is a tricky issue, though; with the recent developments in xz upstream, I'm not sure if the maintainer would be willing at this time to look at contributions that allow it to build (or improve it) on Darwin w/o patching or seding or whatever.

Either way, if we're switching to an Autoconf-based build, that suggests you're trying to remove the arbitrary restriction lzma-static has on only supporting whitelisted platforms, in which case you'd probably want to do the same for GHCup. Perhaps we can have whitelisted supported platforms, but only emit a first-launch warning instead of preventing people from using GHCup entirely, and maybe emit a warning for GHC versions which don't explicitly support the platform and we don't have patches for.

Otherwise, if you want to keep the whitelisted-platforms approach, we can just add the OpenBSD-specific config.h header file to lzma-static, which entirely fixes that build issue for OpenBSD, and we can postpone the project of opening up unofficial support for arbitrary (UNIXy/POSIXy) platforms, or decide not to do that at all.

hasufell commented 1 month ago

Yes, I think the next step is to have lzma-static with proper autoconf.

habibalamin commented 1 month ago

I think GHCup codebase should also be modified to support shims to make GHC build on certain platforms; I'm sure it supports patches to allow older GHC versions to build on officially unsupported platforms, so this would be a similar thing, it would even be setup before the patch phase of the build.

All it would take is prepending some shim directory to the PATH before running the build steps, and allowing the manifests or whatever they're called to add custom shims (and specify what platforms they're needed on, but a shim can without too much difficulty forward to the original command except on the platforms it's needed on).

I'm happy to do the tedious work (over a long span of time, bit by bit) to figure out all the problems with getting (mostly older) versions of GHC to build on (mostly older) versions of OpenBSD and write the build scripts to generate bindists and incorporate those patches, shims, etc. into the GHCup manifests, too (my focus will probably be equally split between amd64 and arm64, but hey, I might play around with other architectures that OpenBSD supports).

That said, GHC versions will probably be released at a faster rate than I could keep up, unless the newer versions don't require too much work to get to build on newer versions of OpenBSD and/or I let support for older GHC and/or OpenBSD versions languish.

I think there's some work upstream to get OpenBSD better supported, so hopefully, it shouldn't be an issue at some point.

dfordivam commented 1 month ago

@habibalamin I see that you have mostly figured out the steps for getting ghcup to compile on openbsd (including generation of lzma-staticautoconf). Could you push the code on forks or open PRs on https://github.com/hasufell/lzma-static and ghcup

dfordivam commented 1 month ago

lzma-static with proper autoconf.

Oh I missed the "proper autoconf" part, so get rid of all config.h and use build-type: Configure. In that case its a bit more work..

habibalamin commented 1 month ago

Appreciate you taking on the job of switching lzma-static's build method to autoconf (though I was happy to do it and still am if you like).

I'll make the changes to GHCup when I get a chance hopefully before this weekend.

habibalamin commented 1 month ago

I was able to build GHCup on OpenBSD 6.8 amd64 only a few days ago, but then I upgraded my installation to 7.5 right afterwards, as it was woefully out of date (latest is 7.6, never got around to finishing).

That was right before I left the message about getting this working on OpenBSD. Now, it would involve getting this building on GHC 9.6 (and OpenBSD 7.6 may be on GHC 9.8 on its ports).

I am now getting a lot of missing imports (“variable not in scope”) build errors on my OpenBSD 7.5 amd64 machine when making my changes on master that I don't get on master on my macOS aarch64 machine.

It seems to mostly be Control.Monad stuff (forM, when, join, …) in different modules, but every time I fix the errors that show up and rebuild, more missing imports in other modules shows up.

I don't have the time to finish this right now and I don't even know how many errors I'll have to fix given how GHC hides some until I fix previous (dependency?) modules, but I'll get back to it at some point in the coming week if I can.

I also got a missing Alternative (Either String) instance.

My OpenBSD machine is running GHC 9.6.4, and my macOS is running GHC 9.6.2, however, I think the real issue is that, for whatever reason, I have to add base to allow-newer: in the Cabal project file (cabal.project.release) (for both machines, since both are on GHC 9.6 series) to get the build plan solver to even allow building to begin.

The proper solution to this is to figure out what's holding the base dependency back, because our explicit version bounds for it should be fine for GHC 9.6 series and later, so we don't have to allow-newer: base and presumably cause a bunch of other packages to upgrade as well (I'm not sure exactly how allow-newer: works).

This means my transformers on OpenBSD is >= 1.6.something, and on my macOS machine, it's 1.5.6.2, which gets rid of the orphan Alternative (Either String) instance in Control.Monad.Trans.Error or something, maybe because my base is 4.18.0.0 on my macOS machine due to using GHC 9.6.2 but my OpenBSD uses base 4.18.2.0 which affects which other packages the solver chooses? (I'm doing allow-newer: base on both machines).

I haven't tracked down why the sudden need to import Control.Monad everywhere, though, or how it works without it on my macOS package versions.

habibalamin commented 1 month ago

Anyway, this is the diff needed for OpenBSD separate from the concern of getting it to build with recent OpenBSD versions which come with packages of recent GHC versions. If I don't get round to dealing with that stuff, which is not really particular to OpenBSD, then someone can create a PR by starting with these changes.

diff --git a/lib-opt/GHCup/OptParse/ChangeLog.hs b/lib-opt/GHCup/OptParse/ChangeLog.hs
index 97bf9d2..797cfef 100644
--- a/lib-opt/GHCup/OptParse/ChangeLog.hs
+++ b/lib-opt/GHCup/OptParse/ChangeLog.hs
@@ -136,6 +136,7 @@ changelog ChangeLogOptions{..} runAppState runLogger = do
               Darwin  -> exec "open" [T.unpack $ decUTF8Safe $ serializeURIRef' uri] Nothing Nothing
               Linux _ -> exec "xdg-open" [T.unpack $ decUTF8Safe $ serializeURIRef' uri] Nothing Nothing
               FreeBSD -> exec "xdg-open" [T.unpack $ decUTF8Safe $ serializeURIRef' uri] Nothing Nothing
+              OpenBSD -> exec "xdg-open" [T.unpack $ decUTF8Safe $ serializeURIRef' uri] Nothing Nothing
               Windows -> do
                 let args = "start \"\" " ++ (T.unpack $ decUTF8Safe $ serializeURIRef' uri)
                 c <- liftIO $ system $ args
diff --git a/lib-tui/GHCup/Brick/Actions.hs b/lib-tui/GHCup/Brick/Actions.hs
index 020ce0b..7e528e4 100644
--- a/lib-tui/GHCup/Brick/Actions.hs
+++ b/lib-tui/GHCup/Brick/Actions.hs
@@ -458,6 +458,7 @@ changelog' (_, ListResult {..}) = do
             Darwin  -> exec "open" [T.unpack $ decUTF8Safe $ serializeURIRef' uri] Nothing Nothing
             Linux _ -> exec "xdg-open" [T.unpack $ decUTF8Safe $ serializeURIRef' uri] Nothing Nothing
             FreeBSD -> exec "xdg-open" [T.unpack $ decUTF8Safe $ serializeURIRef' uri] Nothing Nothing
+            OpenBSD -> exec "xdg-open" [T.unpack $ decUTF8Safe $ serializeURIRef' uri] Nothing Nothing
             Windows -> do
               let args = "start \"\" " ++ (T.unpack $ decUTF8Safe $ serializeURIRef' uri)
               c <- liftIO $ system $ args
diff --git a/lib/GHCup/Platform.hs b/lib/GHCup/Platform.hs
index 93dcd05..15669e6 100644
--- a/lib/GHCup/Platform.hs
+++ b/lib/GHCup/Platform.hs
@@ -119,11 +119,17 @@ getPlatform = do
         either (const Nothing) Just . versioning . decUTF8Safe'
           <$> getFreeBSDVersion
       pure $ PlatformResult { _platform = FreeBSD, _distroVersion = ver }
+    "openbsd" -> do
+      ver <-
+        either (const Nothing) Just . versioning . decUTF8Safe'
+          <$> getOpenBSDVersion
+      pure $ PlatformResult { _platform = OpenBSD, _distroVersion = ver }
     "mingw32" -> pure PlatformResult { _platform = Windows, _distroVersion = Nothing }
     what -> throwE $ NoCompatiblePlatform what
   lift $ logDebug $ "Identified Platform as: " <> T.pack (prettyShow pfr)
   pure pfr
  where
+  getOpenBSDVersion = lift $ fmap _stdOut $ executeOut "uname" ["-r"] Nothing
   getFreeBSDVersion = lift $ fmap _stdOut $ executeOut "freebsd-version" [] Nothing
   getDarwinVersion = lift $ fmap _stdOut $ executeOut "sw_vers"
                                                         ["-productVersion"]
@@ -306,6 +312,7 @@ getStackGhcBuilds PlatformResult{..} = do
             [] -> []
             _ -> L.intercalate "-" c)
           libComponents
+      OpenBSD  -> pure []
       FreeBSD ->
         case _distroVersion of
           Just fVer
@@ -343,6 +350,8 @@ getStackOSKey PlatformRequest { .. } =
     (A_64   , Darwin ) -> pure "macosx"
     (A_32   , FreeBSD) -> pure "freebsd32"
     (A_64   , FreeBSD) -> pure "freebsd64"
+    (A_32   , OpenBSD) -> pure "openbsd32"
+    (A_64   , OpenBSD) -> pure "openbsd64"
     (A_32   , Windows) -> pure "windows32"
     (A_64   , Windows) -> pure "windows64"
     (A_ARM  , Linux _) -> pure "linux-armv7"
@@ -350,6 +359,7 @@ getStackOSKey PlatformRequest { .. } =
     (A_Sparc, Linux _) -> pure "linux-sparc"
     (A_ARM64, Darwin ) -> pure "macosx-aarch64"
     (A_ARM64, FreeBSD) -> pure "freebsd-aarch64"
+    (A_ARM64, OpenBSD) -> pure "openbsd-aarch64"
     (arch', os') -> throwE $ UnsupportedSetupCombo arch' os'

 getStackPlatformKey :: (MonadReader env m, MonadFail m, HasLog env, MonadCatch m, MonadIO m)
diff --git a/lib/GHCup/Types.hs b/lib/GHCup/Types.hs
index 33e496e..5406e48 100644
--- a/lib/GHCup/Types.hs
+++ b/lib/GHCup/Types.hs
@@ -229,6 +229,7 @@ data Platform = Linux LinuxDistro
               | Darwin
               -- ^ must exit
               | FreeBSD
+              | OpenBSD
               | Windows
               -- ^ must exit
   deriving (Eq, GHC.Generic, Ord, Show)
@@ -239,6 +240,7 @@ platformToString :: Platform -> String
 platformToString (Linux distro) = "linux-" ++ distroToString distro
 platformToString Darwin = "darwin"
 platformToString FreeBSD = "freebsd"
+platformToString OpenBSD = "openbsd"
 platformToString Windows = "windows"

 instance Pretty Platform where
diff --git a/lib/GHCup/Types/JSON.hs b/lib/GHCup/Types/JSON.hs
index d30f433..13b96d6 100644
--- a/lib/GHCup/Types/JSON.hs
+++ b/lib/GHCup/Types/JSON.hs
@@ -143,6 +143,7 @@ instance ToJSONKey Platform where
     FreeBSD -> T.pack "FreeBSD"
     Linux (OtherLinux s) -> T.pack ("Linux_" <> s)
     Linux d -> T.pack ("Linux_" <> show d)
+    OpenBSD -> T.pack "OpenBSD"
     Windows -> T.pack "Windows"

 instance FromJSONKey Platform where
Bodigrim commented 1 month ago

I haven't tracked down why the sudden need to import Control.Monad everywhere, though

That's because of an upgrade from mtl-2.2 to mtl-2.3, see https://github.com/haskell/ghcup-hs/pull/1132

habibalamin commented 1 month ago

Ah, makes sense.

I'd initially started by trying to upgrade packages one by one, though I could have sworn I removed them after adding allow-newer: base, and it broke something, but I probably just made a mistake from jumping back and forth.

I just after writing my above message made an accidental change on macOS and figured out the changes to package version bounds in ghcup.cabal were no longer necessary with allow-newer: base, which is why the difference in package versions. So I guess allow-newer: works how I initially expected.

I've just got it building on OpenBSD 7.5 now very simply:

habibalamin commented 1 month ago

Okay, I copied cabal.ghc948.Unix.project and cabal.ghc948.Win32.project for GHC 9.6.4 and made the same allow-newer: base change and pointing lzma-static to a local copy to get the non-release versions to build, too.

@hasufell are you happy to take a PR that adds allow-newer: base in various Cabal project files, or do you want to get the version bounds actually working properly with GHC 9.6?

The PR will also add:

else
  package ghcup
    flags: +tar

to all the GHC 9.6-specific and release-specific Cabal project files that don't already enable that flag, and upgrade lzma-static minimum version bound (though it should just use the later version with autoconf-based build and work if it's just a patch release or something).

I'll wait for @Bodigrim to make the changes to lzma-static to use autoconf and wait for a new release to come out, then I'll update the PR to use that as a minimum version.

hasufell commented 1 month ago

or do you want to get the version bounds actually working properly with GHC 9.6?

See https://github.com/haskell/ghcup-hs/pull/1132