packages feed

ghc-stdin-0.2.0.0: GhcStdin.hs

-----------------------------------------------------------------------------
-- |
-- Module      :  GhcStdin
-- Copyright   :  (c) Alexey Radkov 2022
-- License     :  BSD-style
--
-- Maintainer  :  alexey.radkov@gmail.com
-- Stability   :  experimental
-- Portability :  non-portable (requires GHC with support of plugins)
--
-- A frontend plugin for GHC to compile source code from the standard input.
--
-----------------------------------------------------------------------------


module GhcStdin (frontendPlugin) where

import           GHC.Paths
#if MIN_VERSION_ghc(9,0,2)
import           GHC.Plugins
#else
import           GhcPlugins
#endif
import           Control.Monad
import qualified Data.ByteString as B
import           System.IO
import           System.IO.Temp
import           System.Process
import           System.Exit
import           Safe

-- | Frontend plugin for GHC to compile source code from the standard input.
--
-- In GHC, it is not possible to read source code from the standard input.
--
-- @
-- __$__ echo \'module Main where main = putStrLn \"Ok\"\' | ghc -o simple_ok
-- ghc-9.2.3: no input files
-- Usage: For basic information, try the \`--help\' option.
-- @
--
-- This plugin makes this possible.
--
-- @
-- __$__ echo \'module Main where main = putStrLn \"Ok\"\' | ghc __/--frontend GhcStdin/__ /-ffrontend-opt=\"-o simple_ok\"/
-- [1 of 1] Compiling Main             ( ghc-stdin-d8c31cf0ed893d79\/ghc-stdin260612-0.hs, ghc-stdin-d8c31cf0ed893d79\/ghc-stdin260612-0.o )
-- Linking simple_ok ...
-- __$__ ./simple_ok
-- Ok
-- @
--
-- Notice that GHC flags are passed via /-ffrontend-opt/ in a single string.
--
-- Another use case is collecting exported FFI C functions from a module and
-- putting them in a new shared library.
--
-- @
-- __$__ export NGX_MODULE_PATH=\/var\/lib\/nginx\/x86_64-linux-ghc-9.2.3
-- __$__ echo \'module NgxHealthcheck where import NgxExport.Healthcheck ()\' | ghc __/--frontend GhcStdin/__ /-ffrontend-opt=\"-Wall -O2 -dynamic -shared -fPIC -lHSrts_thr-ghc$(ghc --numeric-version) -L$NGX_MODULE_PATH -lngx_healthcheck_plugin -o ngx_healthcheck.so\"/ 
-- [1 of 1] Compiling NgxHealthcheck   ( ghc-stdin-74de48274545714b\/ghc-stdin266454-0.hs, ghc-stdin-74de48274545714b\/ghc-stdin266454-0.o )
-- Linking ngx_healthcheck.so ...
-- @
--
-- (this is a real-world example taken from
-- [nginx-healthcheck-plugin](https://github.com/lyokha/nginx-healthcheck-plugin)).
--
-- Internally, the plugin creates a temporary directory with a temporary source
-- file inside it with the contents read from the standard input. Then it spawns
-- another GHC process to compile this file with the options passed in
-- /-ffrontend-opt/.
frontendPlugin :: FrontendPlugin
frontendPlugin = defaultFrontendPlugin { frontend = compileCodeFromStdin }

compileCodeFromStdin :: FrontendPluginAction
compileCodeFromStdin flags _ = liftIO $
    withTempDirectory "." "ghc-stdin" $ \dir ->
        withTempFile dir "ghc-stdin.hs" $ \src hsrc -> do
            contents <- B.getContents
            B.hPutStr hsrc contents >> hFlush hsrc
            (_, _, _, h) <- createProcess $
                shell $ q ghc ++ spc (q src) ++ spc (headDef "" flags)
            r <- waitForProcess h
            unless (r == ExitSuccess) $ exitWith r
    where q s = let q' = '\'' in q' : s ++ pure q'
          spc = (' ' :)