From 345786104b728d7a0f230b2cbb3bd4c716811a7c Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 21 Mar 2025 21:35:00 +0000 Subject: [PATCH 1/6] Skip major/minor/makedev on WASM --- src/System/PosixCompat/Extensions.hsc | 8 ++++---- unix-compat.cabal | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/System/PosixCompat/Extensions.hsc b/src/System/PosixCompat/Extensions.hsc index f9aa7bd..7e637c7 100644 --- a/src/System/PosixCompat/Extensions.hsc +++ b/src/System/PosixCompat/Extensions.hsc @@ -12,7 +12,7 @@ module System.PosixCompat.Extensions ( ) where -#ifndef mingw32_HOST_OS +#if !(defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH)) #include "HsUnixCompat.h" #endif @@ -27,7 +27,7 @@ type CMinor = CUInt -- -- The portable implementation always returns @0@. deviceMajor :: DeviceID -> CMajor -#ifdef mingw32_HOST_OS +#if defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH) deviceMajor _ = 0 #else deviceMajor dev = unix_major dev @@ -39,7 +39,7 @@ foreign import ccall unsafe "unix_major" unix_major :: CDev -> CUInt -- -- The portable implementation always returns @0@. deviceMinor :: DeviceID -> CMinor -#ifdef mingw32_HOST_OS +#if defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH) deviceMinor _ = 0 #else deviceMinor dev = unix_minor dev @@ -49,7 +49,7 @@ foreign import ccall unsafe "unix_minor" unix_minor :: CDev -> CUInt -- | Creates a 'DeviceID' for a device file given a major and minor number. makeDeviceID :: CMajor -> CMinor -> DeviceID -#ifdef mingw32_HOST_OS +#if defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH) makeDeviceID _ _ = 0 #else makeDeviceID ma mi = unix_makedev ma mi diff --git a/unix-compat.cabal b/unix-compat.cabal index 85f41fe..b48addc 100644 --- a/unix-compat.cabal +++ b/unix-compat.cabal @@ -69,10 +69,11 @@ Library else build-depends: unix >= 2.7.2.0 && < 2.9 - include-dirs: include - includes: HsUnixCompat.h - install-includes: HsUnixCompat.h - c-sources: cbits/HsUnixCompat.c + if !arch(wasm32) + include-dirs: include + includes: HsUnixCompat.h + install-includes: HsUnixCompat.h + c-sources: cbits/HsUnixCompat.c if os(solaris) cc-options: -DSOLARIS From c38e4c80dab33a70c95938ae480660e9e9fdf208 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 21 Mar 2025 21:49:02 +0000 Subject: [PATCH 2/6] Always bundle include files We just skip compiling it. Yes, this is horrible, but what can you do. --- unix-compat.cabal | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unix-compat.cabal b/unix-compat.cabal index b48addc..87597a4 100644 --- a/unix-compat.cabal +++ b/unix-compat.cabal @@ -69,11 +69,11 @@ Library else build-depends: unix >= 2.7.2.0 && < 2.9 + include-dirs: include + includes: HsUnixCompat.h + install-includes: HsUnixCompat.h if !arch(wasm32) - include-dirs: include - includes: HsUnixCompat.h - install-includes: HsUnixCompat.h - c-sources: cbits/HsUnixCompat.c + c-sources: cbits/HsUnixCompat.c if os(solaris) cc-options: -DSOLARIS From 8416bec1b1a578247b03a1492e10ad849e1c2696 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 21 Mar 2025 22:00:24 +0000 Subject: [PATCH 3/6] Remove NEED_setSymbolicLinkOwnerAndGroup Alternative solution to building. This allows us to remove the include of HsUnixCompat. --- include/HsUnixCompat.h | 2 -- src/System/PosixCompat/Files.hsc | 4 ++-- unix-compat.cabal | 8 ++++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/include/HsUnixCompat.h b/include/HsUnixCompat.h index 32476d4..0adf96d 100644 --- a/include/HsUnixCompat.h +++ b/include/HsUnixCompat.h @@ -4,5 +4,3 @@ unsigned int unix_major(dev_t dev); unsigned int unix_minor(dev_t dev); dev_t unix_makedev(unsigned int maj, unsigned int min); - -#define NEED_setSymbolicLinkOwnerAndGroup !HAVE_LCHOWN diff --git a/src/System/PosixCompat/Files.hsc b/src/System/PosixCompat/Files.hsc index eddfa1e..9c1bf66 100644 --- a/src/System/PosixCompat/Files.hsc +++ b/src/System/PosixCompat/Files.hsc @@ -106,11 +106,11 @@ module System.PosixCompat.Files ( #ifndef mingw32_HOST_OS -#include "HsUnixCompat.h" +#include "HsUnixConfig.h" import System.Posix.Files -#if NEED_setSymbolicLinkOwnerAndGroup +#if !HAVE_LCHOWN import System.PosixCompat.Types setSymbolicLinkOwnerAndGroup :: FilePath -> UserID -> GroupID -> IO () diff --git a/unix-compat.cabal b/unix-compat.cabal index 87597a4..b48addc 100644 --- a/unix-compat.cabal +++ b/unix-compat.cabal @@ -69,11 +69,11 @@ Library else build-depends: unix >= 2.7.2.0 && < 2.9 - include-dirs: include - includes: HsUnixCompat.h - install-includes: HsUnixCompat.h if !arch(wasm32) - c-sources: cbits/HsUnixCompat.c + include-dirs: include + includes: HsUnixCompat.h + install-includes: HsUnixCompat.h + c-sources: cbits/HsUnixCompat.c if os(solaris) cc-options: -DSOLARIS From 43fc04a889a9e81d4fd64168d96f12da07238a87 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 26 Mar 2025 18:03:41 +0000 Subject: [PATCH 4/6] Get tests running on wasm32-wasi We need to use a fork of splitmix here. We /should/ be able to still point to the original repo, but Cabal doesn't run a "git fetch" to download the ref, so you can't point to a commit not on a branch. This is fixed in latest, but that won't be released for a while. --- cabal.project | 8 ++++++++ cbits/mktemp.c | 32 +++++++++++++++++++++++-------- src/System/PosixCompat/Process.hs | 9 ++++++++- src/System/PosixCompat/Temp.hs | 4 ++-- tests/run-wasmtime.sh | 6 ++++++ unix-compat.cabal | 4 +++- 6 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 cabal.project create mode 100755 tests/run-wasmtime.sh diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..3e51bbc --- /dev/null +++ b/cabal.project @@ -0,0 +1,8 @@ +packages: unix-compat.cabal + +if arch(wasm32) + -- See https://github.com/haskellari/splitmix/pull/73 + source-repository-package + type: git + location: https://github.com/amesgen/splitmix.git + tag: cea9e31bdd849eb0c17611bb99e33d590e126164 diff --git a/cbits/mktemp.c b/cbits/mktemp.c index b9ec050..a653efa 100644 --- a/cbits/mktemp.c +++ b/cbits/mktemp.c @@ -41,13 +41,21 @@ #include #include #include + +#if defined(__wasm__) +#include +#else #include #include -static int random(uint32_t *); +#define open _open +#define stat _stat +#endif + +static int unixcompat_random(uint32_t *); static int _gettemp(char *, int *); -static const unsigned char padchar[] = +static const char padchar[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; int unixcompat_mkstemp(char *path) @@ -64,7 +72,7 @@ static int _gettemp(char *path, int *doopen) { char *start, *trv, *suffp, *carryp; char *pad; - struct _stat sbuf; + struct stat sbuf; int rval; uint32_t randidx, randval; char carrybuf[MAXPATHLEN]; @@ -84,7 +92,7 @@ static int _gettemp(char *path, int *doopen) /* Fill space with random characters */ while (trv >= path && *trv == 'X') { - if (!random(&randval)) { + if (!unixcompat_random(&randval)) { /* this should never happen */ errno = EIO; return 0; @@ -104,7 +112,7 @@ static int _gettemp(char *path, int *doopen) for (; trv > path; --trv) { if (*trv == '/') { *trv = '\0'; - rval = _stat(path, &sbuf); + rval = stat(path, &sbuf); *trv = '/'; if (rval != 0) return (0); @@ -120,11 +128,11 @@ static int _gettemp(char *path, int *doopen) for (;;) { if (doopen) { if ((*doopen = - _open(path, O_CREAT|O_EXCL|O_RDWR, 0600)) >= 0) + open(path, O_CREAT|O_EXCL|O_RDWR, 0600)) >= 0) return (1); if (errno != EEXIST) return (0); - } else if (_stat(path, &sbuf)) + } else if (stat(path, &sbuf)) return (errno == ENOENT); /* If we have a collision, cycle through the space of filenames */ @@ -154,7 +162,14 @@ static int _gettemp(char *path, int *doopen) /*NOTREACHED*/ } -static int random(uint32_t *value) +#if defined(__wasm__) +static int unixcompat_random(uint32_t *value) +{ + int r = getentropy(value, sizeof(uint32_t)); + return r == 0 ? 1 : 0; +} +#else +static int unixcompat_random(uint32_t *value) { /* This handle is never released. Windows will clean up when the process * exits. Python takes this approach when emulating /dev/urandom, and if @@ -171,3 +186,4 @@ static int random(uint32_t *value) return 1; } +#endif diff --git a/src/System/PosixCompat/Process.hs b/src/System/PosixCompat/Process.hs index 47d3e43..4a2b585 100644 --- a/src/System/PosixCompat/Process.hs +++ b/src/System/PosixCompat/Process.hs @@ -8,7 +8,7 @@ module System.PosixCompat.Process ( getProcessID ) where -#ifdef mingw32_HOST_OS +#if defined(mingw32_HOST_OS) import System.Posix.Types (ProcessID) import System.Win32.Process (getCurrentProcessId) @@ -16,6 +16,13 @@ import System.Win32.Process (getCurrentProcessId) getProcessID :: IO ProcessID getProcessID = fromIntegral <$> getCurrentProcessId +#elif defined(wasm32_HOST_ARCH) + +import System.Posix.Types (ProcessID) + +getProcessID :: IO ProcessID +getProcessID = pure 1 + #else import System.Posix.Process diff --git a/src/System/PosixCompat/Temp.hs b/src/System/PosixCompat/Temp.hs index 8575fc1..63b60f6 100644 --- a/src/System/PosixCompat/Temp.hs +++ b/src/System/PosixCompat/Temp.hs @@ -11,13 +11,13 @@ module System.PosixCompat.Temp ( mkstemp ) where -#ifndef mingw32_HOST_OS +#if !(defined(mingw32_HOST_OS) || defined(wasm32_HOST_ARCH)) -- Re-export unix package import System.Posix.Temp #elif defined(__GLASGOW_HASKELL__) --- Windows w/ GHC, we have fdToHandle so we +-- Window/WASM w/ GHC, we have fdToHandle so we -- can use our own implementation of mkstemp. import System.IO (Handle) diff --git a/tests/run-wasmtime.sh b/tests/run-wasmtime.sh new file mode 100755 index 0000000..62466df --- /dev/null +++ b/tests/run-wasmtime.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env sh + +TMP="$(mktemp -d --suffix=-unix-compat)" +trap 'rm -rf -- "$TMP"' EXIT +wasmtime --dir "$TMP::/" "$@" +exit "$?" diff --git a/unix-compat.cabal b/unix-compat.cabal index b48addc..c635e45 100644 --- a/unix-compat.cabal +++ b/unix-compat.cabal @@ -69,7 +69,9 @@ Library else build-depends: unix >= 2.7.2.0 && < 2.9 - if !arch(wasm32) + if arch(wasm32) + c-sources: cbits/mktemp.c + else include-dirs: include includes: HsUnixCompat.h install-includes: HsUnixCompat.h From 3dbb14e67903e3559b3f897dd848403de286524f Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 26 Mar 2025 18:05:41 +0000 Subject: [PATCH 5/6] Build/test against wasm32-wasi GHCUp doesn't appear to have GHC 9.12. While we don't strictly speaking need it here (it would be sufficient to test with GHC 9.10), it feels easier to just rely on the Nix flake. --- .github/workflows/ci-wasm32-wasi.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/ci-wasm32-wasi.yml diff --git a/.github/workflows/ci-wasm32-wasi.yml b/.github/workflows/ci-wasm32-wasi.yml new file mode 100644 index 0000000..5133c1c --- /dev/null +++ b/.github/workflows/ci-wasm32-wasi.yml @@ -0,0 +1,27 @@ +name: CI (wasm32-wasi) +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + env: + GHC_WASM_META: 45f73c3e075fa38efe84055b0dba87996948101d + steps: + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v31 + + - name: Build + run: | + nix shell "gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org&rev=${GHC_WASM_META}"#{wasmtime,wasm32-wasi-cabal-9_12,wasm32-wasi-ghc-9_12} --command \ + wasm32-wasi-cabal update + nix shell "gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org&rev=${GHC_WASM_META}"#{wasmtime,wasm32-wasi-cabal-9_12,wasm32-wasi-ghc-9_12} --command \ + wasm32-wasi-cabal build + + - name: Test + run: | + nix shell "gitlab:haskell-wasm/ghc-wasm-meta?host=gitlab.haskell.org&rev=${GHC_WASM_META}"#{wasmtime,wasm32-wasi-cabal-9_12,wasm32-wasi-ghc-9_12} --command \ + wasm32-wasi-cabal test --test-wrapper ./tests/run-wasmtime.sh From 027720fad78127b4bd93b21e98295c9f3c24a25d Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Wed, 26 Mar 2025 18:33:46 +0000 Subject: [PATCH 6/6] Add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7275f3c..0bfbbab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Unreleased + + - Add wasm32-wasi support. + ## Version 0.7.3 (2024-10-11) - Fix `sysmacros.h` include for GNU/Hurd