# 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.
: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:
-- | 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
:
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!
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
-- 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