This project is a practical example of using shake against a fairly small C++ build problem case. The design concepts of shake-cpp can likely be applied to non-C++ build problems as well, so this is being made public for educational purposes to the Shake/Haskell community.
The following is an experimental abstraction layer for the Shake build system , specifically for abstracting away concerns of:
- C++ toolchain, e.g. linker and compiler
- testing tools
- path conventions, e.g.
./src
vs./sources
Main design approach:
- shake-cpp moves
*.cpp
<->*.o
and*.o
<->*.exe
etc isomorphisms to a single data-structure, and generalizes operations on that. This approach works very well in managing the complexity of shake rule patterns:
buildPaths :: FilePath -> BuildPaths
buildPaths build_par =
BuildPaths {
outputPfx = build_par, -- E.g. .build/ or build_ or dist etc
testLib = "test-lib",
-- An isomorphism, the second member is prefixed with outputPfx,
-- so ".build/bin/a/b/last.o" when going from left to right,
-- for input "src/a/b/last.cc"
sourceObj = Iso "src" "bin",
testObj = Iso "tests" "tests",
testExec = Iso (build_par </> "tests") "test-bin",
testStates = Iso (build_par </> "test-bin") "test-state",
archives = "lib"
}
- shake-cpp enhances shake rules with a monad,
BuildM
that passes an environmentEnv
to build targets and tool-chain calls.
srcRules :: BuildM Rules ()
srcRules = do
-- rule for an archive, named "./lib/project-lib.o", comprised of sources
-- under the src/foo/ directory
Rule.archive "project-lib" [Leaves "foo" False False]
-- rule for objects built from sources in "src/"
Rule.object sourceObj [] Deps.llvmObj
testRules :: BuildM Rules ()
testRules = do
src_root <- inputDir sourceObj
test_root <- inputDir testObj
Rule.object testObj []
(Deps.llvmObj <> Deps.boostTestObj src_root test_root)
Rule.testExecs
(Deps.boostTestExec projectName <> Deps.clang <> Deps.llvmExec )
-- Rule for test pass states, this makes "./shake .build/test-state/test_example.pass"
-- as a test runner case possible
Rule.test_states
-- Bind source rules into this monad (this is a super-set of those)
srcRules
-- clean rule, derived from BuildPaths
Rule.clean
and in the module Deps example:
module Deps where
import Development.Shake.Cpp.Build (Def)
import Development.Shake.Cpp.ObjectDeps
import Development.Shake.Cpp.ExecDeps
import Development.Shake.Cpp.Obj
import Data.Monoid
import qualified Data.Set as S
import qualified Data.Map as M
import qualified Data.List as L
boostTestExec :: FilePath -> ExecDeps
boostTestExec subject_archive =
ExecDeps
(M.singleton subject_archive Archive)
$ L.map Lib
["boost_system", "boost_thread", "boost_unit_test_framework"]
boostTestObj :: FilePath -> FilePath -> ObjectDeps
boostTestObj src_root test_root =
ObjectDeps
(S.fromList ["BOOST_TEST_MAIN","BOOST_TEST_DYN_LINK"])
(S.fromList [src_root, test_root])
llvmInc :: FilePath
llvmInc = "/usr/lib/llvm-3.5/include"
llvmDefs :: S.Set Def
llvmDefs =
S.fromList
["NDEBUG",
"_GNU_SOURCE",
"__STDC_CONSTANT_MACROS",
"__STDC_FORMAT_MACROS",
"__STDC_LIMIT_MACROS"]
llvmObj :: ObjectDeps
llvmObj =
ObjectDeps llvmDefs (S.fromList ["/usr/lib/llvm-3.5/include"])
llvmExec :: ExecDeps
llvmExec = ExecDeps mempty [Evaluated "llvm-config --libs all --ldflags"]
clang :: ExecDeps
clang = mempty {
exeLinked =
L.map Lib
["pthread",
"tinfo",
"dl",
"clangTooling",
"clangFrontendTool",
"clangFrontend",
"clangDriver",
"clangSerialization",
"clangCodeGen",
"clangParse",
"clangSema",
"clangStaticAnalyzerFrontend",
"clangStaticAnalyzerCheckers",
"clangStaticAnalyzerCore",
"clangAnalysis",
"clangARCMigrate",
"clangRewriteFrontend",
"clangEdit",
"clangAST",
"clangASTMatchers",
"clangLex",
"clangBasic",
"z"]
}