-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathBuild.hs
84 lines (73 loc) · 3.87 KB
/
Build.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
-- | Provide high-level functions to build Haskell-project using some docker image
module System.Build(BuildArgs(..), asBinaryName, asStackArg
, stackInDocker) where
import Data.Functor
import Data.String
import System.Directory
import System.Docker
import System.IO
import System.IO.Extra
import System.Process
-- | Arguments passed to `stack` when building desired target
data BuildArgs = SimpleTarget String
-- ^A simply named target for stack, assumes component is unique in all current packages. See <Stack https://docs.haskellstack.org/en/stable/build_command/#target-syntax>.
| FullTarget String String
-- ^A fully named target for stack to build. Assumes component is an executable type.
| GHCOption String
-- ^Pass arguments to GHC
| MoreArgs BuildArgs BuildArgs
-- ^Compose arguments
| NoArgs
-- ^Neutral element for `BuildArgs`
deriving (Eq, Show, Read)
instance Monoid BuildArgs where
mempty = NoArgs
mappend = MoreArgs
instance IsString BuildArgs where
fromString = SimpleTarget
asStackArg :: BuildArgs -> [String]
asStackArg NoArgs = []
asStackArg (SimpleTarget t) = [":" ++ t]
asStackArg (FullTarget pref t) = [pref ++ ":exe:" ++ t]
asStackArg (GHCOption opt) = ["--ghc-options", opt]
asStackArg (MoreArgs l r) = asStackArg l ++ asStackArg r
asBinaryName :: BuildArgs -> String
asBinaryName NoArgs = ""
asBinaryName (SimpleTarget t) = t
asBinaryName (FullTarget _ t) = t
asBinaryName (GHCOption _) = ""
asBinaryName (MoreArgs l r) = asBinaryName l ++ asBinaryName r
-- | Build a Haskell project using some docker image.
--
-- In order to maximize reuse, this process creates in the current directory a file called `.cidfile` which contains
-- the id of the latest container that ran the build. When this file exists, the next run will reuse the volumes of
-- the previous run which means built dependencies will normally be available.
--
-- The built target, which is assumed to be a binary executable, is then extracted from the container and copied
-- locally in a file named after `asBinaryName`.
--
-- TODO: run with current user in the container or reuse stack's docker capabilities
stackInDocker :: ImageName -> FilePath -> BuildArgs -> IO FilePath
stackInDocker img@(ImageName imgName) srcDir buildTarget = do
absSrcDir <- canonicalizePath srcDir
buildAlreadyRun <- doesFileExist ".cidfile"
if buildAlreadyRun
then do
cid <- readFile ".cidfile"
removeFile ".cidfile"
callProcess "docker" $ ["run", "--cidfile=.cidfile", "-v", absSrcDir ++ ":/build", "--volumes-from=" ++ cid,
"-v", "/root/.stack", "-w", "/build" , imgName, "stack", "build","--allow-different-user" ] ++ asStackArg buildTarget
else callProcess "docker" $ ["run", "--cidfile=.cidfile", "-v", absSrcDir ++ ":/build",
"-v", "/root/.stack", "-w", "/build" , imgName, "stack", "build","--allow-different-user" ] ++ asStackArg buildTarget
exportBinary img (asBinaryName buildTarget)
exportBinary :: ImageName -> String -> IO FilePath
exportBinary (ImageName imgName) targetName = do
cid <- readFile ".cidfile"
let reuseVolumes = if not (null cid)
then "--volumes-from=" ++ cid
else ""
stackRoot <- filter (/= '\n') <$> readProcess "docker" [ "run", "--rm",reuseVolumes , "-w", "/build", imgName, "stack", "path", "--allow-different-user", "--local-install-root" ] ""
(_, Just hout, _, phdl) <- createProcess $ (proc "docker" ["run", "--rm", reuseVolumes, "busybox","dd", "if=" ++ stackRoot ++ "/bin/" ++ targetName ]) { std_out = CreatePipe }
withBinaryFile targetName WriteMode $ \ hDst -> copy hout hDst
void $ waitForProcess phdl
return targetName