# Compiling and running C programs

As in the example in funflow version 1, we can construct a Flow which compiles and executes a C program. As in the older versions of this example, we will use the gcc Docker image to run our compilation step.

In [1]:
:opt no-lint

{-# LANGUAGE Arrows #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}

-- Funflow libraries
import qualified Data.CAS.ContentStore as CS
import Funflow
  ( Flow,
    dockerFlow,
    ioFlow,
    getDirFlow,
    pureFlow,
    putDirFlow,
    runFlow,
  )
import qualified Funflow.Tasks.Docker as DE

-- Other libraries
import Path (toFilePath, Abs, Dir, Path, Rel, File, absdir, parseAbsDir, relfile, reldir, (</>))
import System.Directory (getCurrentDirectory)
import System.Process (runCommand, ProcessHandle)

Similar to in Funflow version 1.x, inputs to Docker tasks are mounted in from the content store. This means that we need to copy our example c files to the content store before we can compile them:

In [2]:
-- | Helper for getting the absolute path to the src directory
srcDir :: () -> IO (Path Abs Dir)
srcDir _ = do
  cwd <- getCurrentDirectory
  cwdAbs <- parseAbsDir cwd
  return $ cwdAbs </> [reldir|./src|]

-- | A `Flow` which copies the c sources to the content store
copyExampleToStore :: Flow () CS.Item
copyExampleToStore = proc _ -> do
  exampleDir <- ioFlow srcDir -< ()
  putDirFlow -< exampleDir

Now we can define a task which compiles the example C files using gcc:

In [3]:
config :: DE.DockerTaskConfig
config =
  DE.DockerTaskConfig
    { DE.image = "gcc:9.3.0",
      DE.command = "gcc",
      DE.args = [ "/example/double.c", "/example/square.c", "/example/main.c"]
    }

-- | Compile our C program and get the path to the output executable
compile :: Flow CS.Item CS.Item
compile = proc exampleItem -> do
  -- Define a volume for the example directory
  let exampleVolume = DE.VolumeBinding {DE.item = exampleItem, DE.mount = [absdir|/example/|]}
  dockerFlow config -< DE.DockerTaskInput {DE.inputBindings = [exampleVolume], DE.argsVals = mempty}

And finally, we can construct our full Flow graph and execute it!

In [4]:
flow :: Flow Integer ProcessHandle
flow = proc input -> do
  -- 1. Add the example to the content store
  example <- copyExampleToStore -< ()
  
  -- 2. Compile the C sources and get the path to the new executable
  output <- compile -< example
  outputDir <- getDirFlow -< output
  exe <- pureFlow (\x -> toFilePath (x </> [relfile|a.out|])) -< outputDir
  
  -- 3. Call the executable
  command <- pureFlow (\(c, n) -> c <> " " <> show n) -< (exe, input)
  ioFlow runCommand -< command
In [5]:
-- Our C program defined in `src/main.c` defines a function f(x) = 2*x + x^2
-- For input 3 this should output 15.
runFlow flow 3 :: IO ProcessHandle
Found docker images, pulling...
Pulling docker image: gcc:9.3.0
15