-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathshake
More file actions
executable file
·215 lines (183 loc) · 7.41 KB
/
shake
File metadata and controls
executable file
·215 lines (183 loc) · 7.41 KB
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#!/usr/bin/env runhaskell +RTS -I0 -RTS
{-# OPTIONS_GHC -with-rtsopts=-I0 -threaded -rtsopts #-}
-- Copyright 2013-2014 Samplecount S.L.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
import Control.Applicative
import Control.Concurrent
import Control.Concurrent.MVar
import Control.Monad
import Data.Char (toLower)
import qualified Data.List as List
import qualified Distribution.PackageDescription as Dist
import qualified Distribution.PackageDescription.Configuration as Dist
import qualified Distribution.PackageDescription.Parse as Dist
import qualified Distribution.Verbosity as Dist
import GHC.Conc (getNumProcessors)
import qualified System.Directory as Dir
import qualified System.Environment as Env
import System.Exit (ExitCode(..), exitFailure)
import System.FilePath
import System.IO
import qualified System.IO.Error as IO
import qualified System.Process as Proc
-- Cabal configuration fields:
configFieldPackageDirs :: String
configFieldPackageDirs = "x-shake-package-dirs"
-- Process utilities
execError :: FilePath -> Int -> IO ()
execError path code = error $ takeFileName path ++ " failed with exit code " ++ show code
checkExitCode :: FilePath -> ExitCode -> IO ()
checkExitCode _ ExitSuccess = return ()
checkExitCode path (ExitFailure code) = execError path code
-- traceCommand path args = hPutStrLn stderr $ "TRACE: " ++ unwords ([path] ++ args)
traceCommand _ _ = return ()
-- Not yet in process-1.1
callProcess :: String -> [String] -> IO ()
callProcess path args = do
traceCommand path args
Proc.rawSystem path args >>= checkExitCode path
-- Ignore exit code
callProcess_ :: String -> [String] -> IO ()
callProcess_ path args = do
traceCommand path args
_ <- Proc.rawSystem path args
return ()
callProcessFilter :: String -> [String] -> (Handle -> String -> IO ()) -> IO ExitCode
callProcessFilter cmd args action = do
traceCommand cmd args
(_, Just out, Just err, pid) <-
Proc.createProcess $ (Proc.proc cmd args) {
Proc.std_out = Proc.CreatePipe
, Proc.std_err = Proc.CreatePipe }
mapM_ (flip hSetBinaryMode False) [out, err]
mapM_ (flip hSetBuffering LineBuffering) [out, err]
forM_ [(out, stdout), (err, stderr)] $ \(hin, hout) -> do
let isError = flip any [IO.isEOFError, IO.isIllegalOperation] . flip ($)
forkIO $ flip IO.catchIOError (\e -> if isError e then return () else ioError e)
$ forever $ hGetLine hin >>= action hout
ec <- Proc.waitForProcess pid
hClose out
hClose err
return ec
findExecutable :: String -> IO FilePath
findExecutable exe = maybe (error $ exe ++ " executable not found") id
<$> Dir.findExecutable exe
getCabalFile :: IO (FilePath, Dist.PackageDescription)
getCabalFile = do
cabalFiles <- filter (List.isSuffixOf ".cabal")
<$> Dir.getDirectoryContents "."
case cabalFiles of
[] -> error "No cabal file found"
(_:_:_) -> error $ "Multiple cabal files found: " ++ List.intercalate ", " cabalFiles
[cabalFile] -> do
pkg <- Dist.flattenPackageDescription
<$> Dist.readPackageDescription Dist.silent cabalFile
return (cabalFile, pkg)
getBuildCommand :: FilePath -> Dist.PackageDescription -> IO FilePath
getBuildCommand cabalFile pkg =
case Dist.executables pkg of
[] -> error $ "No executables found in " ++ cabalFile
(spec:rest) -> do
let exe = Dist.exeName spec
when (not (null rest)) $
hPutStrLn stderr $ "Multiple executables found in " ++ cabalFile ++ ", using " ++ exe
return $ buildDir </> "build" </> exe </> exe
configPackageDirs :: [(String, String)] -> [FilePath]
configPackageDirs = maybe [] id
. fmap lines
. lookup configFieldPackageDirs
sandboxDir :: FilePath
sandboxDir = ".cabal-sandbox"
buildDir :: FilePath
buildDir = "dist"
main :: IO ()
main = do
(cabalFile, pkg) <- getCabalFile
let config = Dist.customFieldsPD pkg
cabal <- findExecutable "cabal"
progName <- Env.getProgName
args <- Env.getArgs
j <- (("-j"++) . show) <$> getNumProcessors
-- let j = "-j1"
let configureArgs = [
-- These might be defined in the user's cabal config file and effectively double compilation times
-- "--disable-library-profiling"
-- , "--disable-executable-profiling"
]
configure = do
putStrLn $ progName ++ ": Configuring build system ..."
callProcess cabal $ ["configure"] ++ configureArgs
initialize = do
putStrLn $ progName ++ ": Initializing build system ..."
callProcess cabal ["sandbox", "init"]
mapM_ (\dir -> callProcess cabal ["sandbox", "add-source", dir])
(configPackageDirs config)
callProcess cabal $ ["install"] ++ configureArgs ++
[ "--only-dependencies"
, "--force-reinstalls"
, "--disable-documentation"
, j]
configure
update = do
putStrLn $ progName ++ ": Updating build system ..."
sandboxExists <- Dir.doesDirectoryExist sandboxDir
exeExists <- Dir.doesFileExist =<< getBuildCommand cabalFile pkg
if not sandboxExists
then initialize
else if not exeExists
then configure
else return ()
-- Update build executable
-- When the public interface of package dependencies changes, the local package needs to be reconfigured.
reconfigure <- newMVar False
exitCode <- callProcessFilter cabal ["build", j] $ \h line -> do
hPutStrLn h line
when (List.isInfixOf "cannot satisfy -package-id" line)
$ void $ swapMVar reconfigure True
case exitCode of
ExitSuccess -> return ()
ExitFailure code -> do
b <- readMVar reconfigure
if b
then do
configure
callProcess cabal ["build", j]
else execError cabal code
case args of
(".init":_) -> do
-- Initialize sandbox
initialize
(".update":_) -> do
-- Update build command
update
(".scrub":_) -> do
-- Clean everything
exe <- getBuildCommand cabalFile pkg
exeExists <- Dir.doesFileExist exe
when exeExists $ callProcess_ exe ["clean"]
distExist <- Dir.doesDirectoryExist buildDir
when distExist $
Dir.removeDirectoryRecursive buildDir
hasSandbox <- Dir.doesDirectoryExist sandboxDir
when hasSandbox $
callProcess cabal ["sandbox", "delete"]
(('.':cmd):_) -> do
hPutStrLn stderr $ "Usage: " ++ progName ++ " .init|.update|.scrub|SHAKE_ARGS..."
exitFailure
args -> do
-- Call build command with arguments
exe <- getBuildCommand cabalFile pkg
exeExists <- Dir.doesFileExist exe
unless exeExists $ update
callProcess exe (j:args)