4

I'm working on a Haskell library that contains parts that target WebAssembly (WASM) using Asterius. These parts can't be compiled with the normal ghc and for that reason we have flags that exclude/include the WASM parts.

Trying to build the documentation with Asterius' ahc-cabal new-haddock fails. It seems to revert to the normal ghc for the Haddock command.

My question is: Can I build the Haddock documentation without compiling the source it describes?


Excerpts of my file that might be relevante:

Section of my cabal file:

flag wasm
  description:         Eanbles builds targeting WASM.
  default:             False
  manual:              True

library
  exposed-modules:     Boardgame
  build-depends:       base >= 4.12 && < 5.0
  if flag(wasm)
    exposed-modules:   Boardgame.Web
    build-depends:
        aeson >= 1.4 && < 1.6
      , asterius-prelude == 0.0.1
      , scientific >= 0.3 && < 0.4
    CPP-options:       "-DWASM"

Section of Boardgame.hs:

#ifdef WASM
import Data.Aeson (ToJSON(toJSON), Value(Number, Null))
import Data.Scientific (fromFloatDigits)
#endif

-- | Represents one of the two players.
data Player = Player1 | Player2
  deriving (Show, Eq)

#ifdef WASM
instance ToJSON Player where
  toJSON = Number . fromFloatDigits . fromIntegral . playerToInt
#endif

Section of Boardgame/Web.hs:

module Boardgame.Web (
    addWebGame
) where

foreign import javascript "wrapper" jsMakeCallback :: IO () -> IO JSVal
foreign import javascript "boardgame.games[$1] = $2" jsSetGame :: JSVal -> JSVal -> IO ()

-- | Adds a named game to the list of games accessible from JavaScript.
addWebGame :: (ToJSON a, ToJSON c, FromJSON c, PositionalGame a c) => String -> a -> IO ()
addWebGame name startState = do
  callback <- jsMakeCallback $ playWeb startState
  jsSetGame (jsonToJSVal name) callback

My main file (Boardgame.hs) contains code "hidden" behind the WASM flag but no of code has any documentation. Boardgame/Web.hs is only included when the WASM flag is specified and has some functions with documentation.

I want to build the documentation for all documented functions in both Boardgame.hs and Boardgame/Web.hs.

Netråm
  • 453
  • 6
  • 12
  • 1
    The command `haddock fileList` will do this, but I assume you want it to automatically get the file list from your cabal project and install the haddocks into the correct directory. – Ari Fordsham Apr 10 '21 at 23:28
  • 1
    Would it help to redefine all excluded symbols to `undefined` in the no-WASM branch? you can keep the type signatures unconditional, and just redefine the terms. – Ari Fordsham Apr 10 '21 at 23:31
  • @AriFordsham I can't find that command, do you have an example/documentation about it? And yes, I'd prefer to let cabal take care of things bu at this point I'll take anything that works. – Netråm Apr 11 '21 at 06:04
  • @AriFordsham Commenting out WASM imports and redefining symbols to `undefined` would work. Do you have any tips on doing that? Or do I have to do it by hand? – Netråm Apr 11 '21 at 06:05
  • 1
    Can you post some example source? – Ari Fordsham Apr 11 '21 at 11:05
  • @AriFordsham Yes, I've updated the question with excerpts of my files I think are relevant. – Netråm Apr 11 '21 at 11:22
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231011/discussion-between-ari-fordsham-and-netram). – Ari Fordsham Apr 11 '21 at 17:55

1 Answers1

1

Instead of excluding your module Boardgame/Web.hs, which contains the imports that GHC can't process, entirely when the WASM flag is set, you could use CPP in Boardgame/Web.hs to conditionally set all non-GHC-compatible symbols to undefined.

The way I would do it is to move all type signatures to the top of the module, and make two sets of definitions, like so:

module Boardgame.Web (
    addWebGame
) where

addWebGame :: (ToJSON a, ToJSON c, FromJSON c, PositionalGame a c) => String -> a -> IO ()
-- other signatures...

#ifdef WASM
foreign import javascript "wrapper" jsMakeCallback :: IO () -> IO JSVal
foreign import javascript "boardgame.games[$1] = $2" jsSetGame :: JSVal -> JSVal -> IO ()

addWebGame name startState = do
  callback <- jsMakeCallback $ playWeb startState
  jsSetGame (jsonToJSVal name) callback

-- other definitions...

#else

addWebGame = undefined

#endif

Boardgame.Web should then be imported unconditionally.

This should build with GHC. It will obviously error without doing anything useful if run, but it's enough to let cabal/haddock extract the documentation.

The downside is changing the way you structure your code.

I'm sure this can be automated with something like an awk script, but it's beyond me.

Alternatively, if it's just the FFI declarations that are the problem, you could do this much simpler (and more automably):

module Boardgame.Web (
    addWebGame
) where

#ifdef WASM
foreign import javascript "wrapper" jsMakeCallback :: IO () -> IO JSVal
foreign import javascript "boardgame.games[$1] = $2" jsSetGame :: JSVal -> JSVal -> IO ()

#else
jsMakeCallback = undefined
jsSetGame = undefined
#endif

-- addWebGame and others unmodified.

To get you started with automating, a command like cat Boardgame/Web.hs | sed "s/foreign import javascript \"[^\"]*\" \([^ ]*\).*/\1 = undefined/" will extract a list of javascript imports and convert them into undefined assignments.

Happy Haskelling!

Ari Fordsham
  • 2,437
  • 7
  • 28