import Development.Shake
import Development.Shake.Command
import Development.Shake.FilePath
import Development.Shake.Util
import System.IO (readFile)
import System.Directory (findExecutable, getCurrentDirectory, removeDirectoryRecursive)
import Data.Foldable (asum)
import System.Console.GetOpt
import Data.Hashable (hash)
import System.Environment
import System.Process (readProcess)
import Data.Maybe (listToMaybe, fromMaybe)
import Data.Function ((&))
flags = fmap (filter (not . null) . lines) . readFile
findExe :: [String] -> IO (Maybe FilePath)
findExe cmds = asum <$> mapM findExecutable cmds
cdir = "src"
incdir = "include"
builddir = "_build"
data BuildTypes = Default | Test | Bench deriving (Read)
data OptLevels = Release | Debug deriving (Read)
data Options
= BuildType BuildTypes
| OptLevel OptLevels
| DisableCCACHE
| EmitLLVM
| BuildFromASM
| Prefix FilePath
| Runner String
options :: [OptDescr (Either a Options)]
options =
[ Option "t" ["build-type"] (ReqArg (Right . BuildType . read) "default") "[default|test|bench]"
, Option "o" ["opt-level"] (ReqArg (Right . OptLevel . read) "g") "[0-3|g]"
, Option "" ["disable-ccache"] (NoArg $ Right DisableCCACHE) ""
, Option "" ["emit-llvm"] (NoArg $ Right EmitLLVM) ""
, Option "" ["build-from-asm"] (NoArg $ Right BuildFromASM) ""
, Option "p" ["prefix"] (ReqArg (Right . Prefix) "/usr/local") ""
, Option "r" ["runner"] (ReqArg (Right . Runner) "gdb") ""
]
main :: IO ()
main = shakeArgsWith shakeOptions options $ \opts targets -> pure $ Just $ do
cwd <- liftIO $ getCurrentDirectory
let proj = takeFileName cwd
gitBranch <- liftIO $ readProcess "git" ["branch", "--show-current"] ""
inNixShell <- liftIO $ System.Environment.getEnv "IN_NIX_SHELL"
cflags <- liftIO $ flags "build/cflags"
ldflags <- liftIO $ flags "build/ldflags"
asmflags <- liftIO $ flags "build/asmflags"
optflags <- liftIO $ flags "build/optflags"
debugflags <- liftIO $ flags "build/debugflags"
optldflags <- liftIO $ flags "build/optldflags"
let typeFlag = [a | BuildType a <- opts] & listToMaybe & fromMaybe Default
let optFlag = [a | OptLevel a <- opts] & listToMaybe & fromMaybe Debug
let prefix = [a | Prefix a <- opts] & listToMaybe & fromMaybe (error "")
let runner = [a | Runner a <- opts]
compile <- do
Just cc <- liftIO $ findExe ["clang-21", "clang-20", "clang-19", "clang"]
ccache <- liftIO $ findExe ["ccache"]
let p DisableCCACHE = True
p _ = False
let disable_ccache = any p opts
case (disable_ccache, ccache) of
(False, Just ch) -> return [ch, cc]
_ -> return [cc]
let typeFlags Default = []
typeFlags Test = ["-DTEST_MODE"]
typeFlags Bench = ["-DBENCHMARK_MODE"]
let optFlags Release = optflags
optFlags Debug = debugflags
let optLDFlags Release = optldflags
optLDFlags Debug = debugflags
let compileFlags = cflags ++ typeFlags typeFlag ++ optFlags optFlag ++ ["-I" ++ incdir]
let linkFlags = ldflags ++ optLDFlags optFlag
let seed = unwords compile ++ unwords compileFlags ++ unwords linkFlags ++ gitBranch ++ inNixShell
let h = abs $ hash seed
let outDir = builddir </> show h
let target = outDir </> proj
let bin = builddir </> proj
want targets
phony "all" $ do
need [bin]
bin %> \out -> do
need [target]
copyFileChanged target out
target %> \out -> do
cs <- getDirectoryFiles cdir ["*.c"]
let os = [outDir </> c -<.> "o" | c <- map takeFileName cs]
need os
cmd_ compile "-o" [out] os linkFlags
outDir </> "*.o" %> \out -> do
let c = cdir </> (takeFileName $ out -<.> "c")
let m = out -<.> "m"
cmd_ compile "-o" [out] "-c" [c] "-MMD -MF" [m] compileFlags
neededMakefileDependencies m
phony "install" $ do
copyFileChanged bin (prefix </> "bin" </> proj)
phony "clear" $ do
liftIO $ removeDirectoryRecursive outDir
phony "clear-all" $ do
liftIO $ removeDirectoryRecursive builddir