diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010-2016 Brett Lajzer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,228 @@
+-- Copyright (c) 2010-2016 Brett Lajzer
+-- See LICENSE for license information.
+
+-- | This is the command-line executable "dib". Since Dib proper is a
+-- library, one doesn't need this, but it makes dealing with build
+-- scripts quite a bit easier.
+module Main where
+
+import Paths_dib (version)
+import GHC.IO.Exception
+import Control.Monad
+import Data.Maybe
+import Data.Time.Clock.POSIX
+import Data.Version (showVersion)
+import System.Process (system)
+import qualified System.Directory as D
+import System.Environment (getArgs)
+import System.FilePath as F
+import System.Info
+import System.IO
+
+unixExe :: String
+unixExe = ".dib/dib"
+
+windowsExe :: String
+windowsExe = ".dib/dib.exe"
+
+correctExe :: String
+correctExe = if os == "mingw32" then windowsExe else unixExe
+
+correctExtension :: String
+correctExtension = if os == "mingw32" then ".exe" else ""
+
+-- | Location to copy the dib.hs script to and rename it. Get around
+-- an issue with building on Windows.
+tmpDibScript :: String
+tmpDibScript = "dib-tmp.hs"
+
+-- | The file that stores the timestamp for dib.hs
+timestampFile :: String
+timestampFile = ".dib/timestamp"
+
+-- | The file that stores the version of the dib library
+versionFile :: String
+versionFile = ".dib/version"
+
+-- | The command line for building dib.hs
+buildString :: String
+buildString = "ghc -o dib" ++ correctExtension ++ " -O2 -XOverloadedStrings -rtsopts -threaded -outputdir . " ++ tmpDibScript
+
+-- | A basic dib script. This is the script saved when running "dib init"
+defaultScript :: String
+defaultScript = "\
+  \module Main where\n\n\
+  \import Dib\n\
+  \import qualified Data.Text as T\n\n\
+  \targets = []\n\n\
+  \main = dib targets\n"
+
+compilerToConfig :: String -> String
+compilerToConfig "gcc" = "defaultGCCConfig"
+compilerToConfig "g++" = "defaultGXXConfig"
+compilerToConfig "clang" = "defaultClangConfig"
+compilerToConfig template = error $ "Error: unknown template \"" ++ template ++ "\""
+
+-- | Makes a C Builder. Takes a name and source directory.
+cBuilderScript :: String -> String -> String -> String
+cBuilderScript name compiler srcDir = "\
+  \module Main where\n\n\
+  \import Dib\n\
+  \import Dib.Builders.C\n\
+  \import qualified Data.Text as T\n\n"
+  ++ "projectInfo = " ++ compilerToConfig compiler ++ " {\n"
+  ++ "  outputName = \"" ++ name ++ "\",\n"
+  ++ "  targetName = \"" ++ name ++ "\",\n"
+  ++ "  srcDir = \"" ++ srcDir ++ "\",\n"
+  ++ "  compileFlags = \"\",\n"
+  ++ "  linkFlags = \"\",\n"
+  ++ "  outputLocation = ObjAndBinDirs \"obj\" \".\",\n"
+  ++ "  includeDirs = [\"" ++ srcDir ++ "\"]\n"
+  ++ "}\n\n"
+  ++ "project = makeCTarget projectInfo\n\
+  \clean = makeCleanTarget projectInfo\n\n\
+  \targets = [project, clean]\n\n\
+  \main = dib targets"
+
+-- | Recurses up the file path until it finds a directory with a dib.hs in it
+-- Once it finds a dib.hs, it returns the path.
+findDib :: FilePath -> IO (Maybe FilePath)
+findDib lastPath = do
+  let dibPath = lastPath </> "dib.hs"
+  hasDib <- D.doesFileExist dibPath
+  if hasDib then
+      return $ Just dibPath
+    else do
+      curPath <- D.canonicalizePath $ lastPath </> ".."
+      if curPath /= lastPath then
+          findDib curPath
+        else
+          return Nothing
+
+-- | Recurses up the file path until it finds a directory with a dib.hs in it
+-- Once it finds a dib.hs, it builds and runs it.
+findAndRunDib :: String -> IO ExitCode
+findAndRunDib args = do
+    curPath <- D.getCurrentDirectory
+    dibPath <- findDib curPath
+    if isJust dibPath then do
+        D.setCurrentDirectory $ F.dropFileName $ fromJust dibPath
+        retVal <- buildAndRunDib args
+        D.setCurrentDirectory curPath
+        return retVal
+      else do
+        putStrLn "Error: can't find dib.hs."
+        putStrLn "Suggestion: use 'dib --init ...' to create one."
+        putStrLn "  Empty Project: 'dib --init empty'"
+        putStrLn "  C Project: 'dib --init c <projectName> (gcc|g++|clang) <srcDir>'"
+        return (ExitFailure 255)
+
+getDibCalendarTimeStr :: IO String
+getDibCalendarTimeStr = do
+  modTime <- D.getModificationTime "dib.hs"
+  let calTime = (fromIntegral.fromEnum.utcTimeToPOSIXSeconds) modTime :: Integer
+  return $ show calTime
+
+checkDibTimestamps :: IO Bool
+checkDibTimestamps = do
+  dibUnixExists <- D.doesFileExist unixExe
+  dibWinExists <- D.doesFileExist windowsExe
+  if dibUnixExists || dibWinExists then do
+      calTimeStr <- getDibCalendarTimeStr
+      storedCalTime <- getStoredCalTime
+      storedVersion <- getStoredVersion
+      let versionStr = showVersion version
+      return $ calTimeStr /= storedCalTime || versionStr /= storedVersion
+    else
+      return True
+
+getStoredCalTime :: IO String
+getStoredCalTime = getStoredTokenFileContents timestampFile
+
+getStoredVersion :: IO String
+getStoredVersion = getStoredTokenFileContents versionFile
+
+getStoredTokenFileContents :: String -> IO String
+getStoredTokenFileContents f = do
+  fileExists <- D.doesFileExist f
+  if fileExists then do
+      file <- openFile f ReadMode
+      tokenStr <- hGetLine file
+      hClose file
+      return tokenStr
+    else
+      return ""
+
+processExitCode :: ExitCode -> IO ()
+processExitCode (ExitSuccess) = return ()
+processExitCode (ExitFailure n) = error $ "Error " ++ show n ++ " building dib.hs."
+
+rebuild :: Bool -> IO ()
+rebuild needToRebuild =
+  when needToRebuild $
+   do D.copyFile "dib.hs" $ ".dib/" ++ tmpDibScript
+      D.setCurrentDirectory ".dib"
+      exitCode <- system buildString
+      D.setCurrentDirectory ".."
+      processExitCode exitCode
+      
+      calTimeStr <- getDibCalendarTimeStr
+      tsFile <- openFile timestampFile WriteMode
+      hPutStr tsFile calTimeStr
+      hClose tsFile
+      
+      let versionStr = showVersion version
+      verFile <- openFile versionFile WriteMode
+      hPutStr verFile versionStr
+      hClose verFile 
+
+requoteArg :: String -> String
+requoteArg arg = requoteArgInternal arg False
+  where
+    requoteArgInternal ('=':xs) False = '=' : '\"' : requoteArgInternal xs True
+    requoteArgInternal [] True = "\""
+    requoteArgInternal [] False = []
+    requoteArgInternal (x:xs) e = x : requoteArgInternal xs e
+
+buildAndRunDib :: String -> IO ExitCode
+buildAndRunDib args = do
+  D.createDirectoryIfMissing False ".dib"
+  needToRebuild <- checkDibTimestamps
+  rebuild needToRebuild
+  system $ correctExe ++ " +RTS -N -RTS " ++ args
+
+shouldHandleInit :: [String] -> Bool
+shouldHandleInit args = not (null args) && head args == "--init"
+
+argsToBuildScript :: [String] -> String
+argsToBuildScript ["c", name, compiler, srcDir] = cBuilderScript name compiler srcDir
+argsToBuildScript ["empty"] = defaultScript
+argsToBuildScript [] = defaultScript
+argsToBuildScript _ = error "Error: Unknown template name."
+
+handleInit :: [String] -> IO Bool
+handleInit args =
+  if shouldHandleInit args then do
+      dibInCurDir <- D.doesFileExist "dib.hs"
+      if dibInCurDir then
+          error "Error: dib.hs already exists in current directory."
+        else do
+          curPath <- D.getCurrentDirectory
+          dibPath <- findDib curPath
+          let initPrefix = if isNothing dibPath then "" else "Warning: dib.hs exists in a parent directory, are you sure you want to do this?\n"
+          let buildScript = argsToBuildScript $ tail args
+          putStrLn $ initPrefix ++ "Initializing dib.hs..."
+          withFile "dib.hs" WriteMode (\h -> hPutStr h buildScript >> return True)
+    else
+      return False
+
+main :: IO ExitCode
+main = do
+    hSetBuffering stdout LineBuffering
+    hSetBuffering stderr LineBuffering
+    args <- getArgs
+    inited <- handleInit args
+    if inited then
+        return ExitSuccess
+      else
+        findAndRunDib (unwords $ map requoteArg args)
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/dib.cabal b/dib.cabal
new file mode 100644
--- /dev/null
+++ b/dib.cabal
@@ -0,0 +1,34 @@
+name:           dib
+version:        0.5.0
+cabal-version:  >= 1.6
+category:		Development
+build-type:     Simple
+license:        MIT
+license-file:	LICENSE
+author:         Brett Lajzer
+maintainer:	    Brett Lajzer
+synopsis: A simple, forward build system.
+description: Dib is a simple, forward build system consisting of a library and a driver application. Build scripts are written in Haskell instead of a bespoke language.
+
+source-repository head
+  type: git
+  location: https://github.com/blajzer/dib.git
+
+source-repository this
+  type: git
+  location: https://github.com/blajzer/dib.git
+  tag: 0.5.0
+
+library
+  build-depends:   base >= 4.4 && < 4.10, text, containers, mtl, directory, time, process, filepath, cereal, bytestring, digest
+  ghc-options:     -Wall
+  hs-source-dirs:  src
+  exposed-modules: Dib, Dib.Gatherers, Dib.Stage, Dib.Target, Dib.Types, Dib.Scanners.CDepScanner, Dib.Builders.C, Dib.Builders.Copy, Dib.Builders.Simple, Dib.Util
+  extensions:      OverloadedStrings
+
+executable dib
+  build-depends:   base >= 4.4 && < 4.10, containers, mtl, time, directory, filepath
+  ghc-options:     -Wall
+  hs-source-dirs:  .
+  main-is:         Main.hs
+
diff --git a/src/Dib.hs b/src/Dib.hs
new file mode 100644
--- /dev/null
+++ b/src/Dib.hs
@@ -0,0 +1,465 @@
+-- Copyright (c) 2010-2016 Brett Lajzer
+-- See LICENSE for license information.
+
+-- | = Introduction
+-- Dib is a light-weight, forward build system embedded in Haskell.
+-- Dib represents the build products as a chain of operations starting at the input.
+-- Reverse build systems such as Make and Jam instead attempt to figure out the
+-- operations to perform starting at the desired output and tracing back through
+-- a set of rules to find the correct input file. Dib has no such notion of "rules"
+-- and the general thought process for writing a build script answers the question
+-- "I have these files, how do I build them into the thing I want?", versus a reverse
+-- build system which answers (recursively) "I want this product, what files do I
+-- need to use as input?".
+--
+-- = Concepts
+-- * 'Target' - The most granluar unit of a build. Represents a desired outcome:
+--   e.g. an executable, a folder of files, etc... Contains 'Stage's, which do
+--   the actual work. Somewhat unfortunately, a 'Target'\'s name is its only identifier
+--   in the cache database, so debug/release and multiplatform 'Target' variants
+--   should be named accordingly to prevent full-rebuilds when switching between them.
+-- * 'Stage' - A portion of a pipeline for transforming input data into output data.
+--   These separate major portions of a pipeline: e.g. building source code into
+--   object files, linking object files into an executable, copying some data into place.
+--   'Target's can have multiple 'Stage's, which are executed in sequence, the output
+--   of one is used as the input to the next.
+-- * 'Gatherer' - Used to generate the initial input 'SrcTransform's for the first
+--   'Stage' of a 'Target'.
+-- * 'SrcTransform' - Represents a mapping from input to output. Comes in four varieties:
+--   'OneToOne', 'OneToMany', 'ManyToOne', 'ManyToMany'. Some examples: compiling a
+--   C source file into an object file initially begins as a 'OneToOne', but is converted
+--   into a 'ManyToOne' through dependency scanning (adding the dependencies to the input
+--   exploits the internal timestamp database for free). Copying files from one location to
+--   another would just be a simple 'OneToOne'. A tool that takes in one file and
+--   generates a bunch of output files would use 'OneToMany'.
+--
+-- = Getting Started
+-- Dib is both a library and an executable. The executable exists to cause a rebuild
+-- of the build script whenever it changes, and also as a convenience for invoking both
+-- the build and execution correctly. It's recommended that it be used for everything
+-- except extraordinary use cases. It can also generate an initial build script
+-- through the use of @dib --init@. Run the dib executable with no options for more
+-- information on the available templates.
+--
+-- The most trivial, do nothing build script looks like the following:
+--
+-- @
+-- module Main where
+-- import Dib
+--
+-- targets = []
+-- main = dib targets
+-- @
+--
+-- A build script is expected to declare the available 'Target's and then pass them
+-- to the 'dib' function. Only the top-level 'Target's need to be passed to 'dib';
+-- it will scrape out the dependencies from there. The first 'Target' in the list
+-- is the default 'Target' to build if the dib executable is called with no arguments.
+--
+-- == Additional Information
+-- Arguments can be passed on the command line to the dib executable. These can be
+-- retrieved in the build with 'getArgDict'. The user is also free to use environment variables
+-- as parameter input.
+--
+-- The invocation might look like the following: @dib <target> <key>=<value> <key>=<value> ...@.
+-- Please note that there are no spaces between the keys and values. Quoted strings are
+-- untested and unlikely to work correctly.  The 'Target' is optional, and can appear
+-- anywhere in the command. If no 'Target' is specified, the default will be used.
+--
+--
+module Dib (
+  SrcTransform(OneToOne, OneToMany, ManyToOne, ManyToMany),
+  dib,
+  getArgDict,
+  addEnvToDict,
+  makeArgDictLookupFunc
+  ) where
+
+import Dib.Gatherers
+import Dib.Target
+import Dib.Types
+import Control.Concurrent
+import Control.Monad
+import Control.Monad.State as S
+import qualified Data.ByteString as B
+import qualified Data.Digest.CRC32 as Hash
+import qualified Data.List as L
+import qualified Data.Map as Map
+import qualified Data.Serialize as Serialize
+import qualified Data.Set as Set
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+import qualified GHC.Conc as GHC
+import qualified System.Directory as D
+import qualified System.Environment as Env
+import Data.Maybe
+import Data.Time.Clock
+import Data.Time.Clock.POSIX
+import Data.Word
+import System.IO
+
+databaseFile :: String
+databaseFile = ".dib/dibdb"
+
+databaseVersion :: Integer
+databaseVersion = 3
+
+-- | The function that should be called to dispatch the build. Takes a list
+-- of the top-level (root) 'Target's.
+dib :: [Target] -> IO ()
+dib targets = do
+  hSetBuffering stdout LineBuffering
+  hSetBuffering stderr LineBuffering
+  args <- Env.getArgs
+  numProcs <- GHC.getNumProcessors
+  let allTargets = gatherAllTargets targets
+  let buildArgs = parseArgs args allTargets numProcs
+  let selectedTarget = buildTarget buildArgs
+  let theTarget = L.find (\(Target name _ _ _ _) -> name == selectedTarget) allTargets
+  if isNothing theTarget
+    then putStrLn $ "ERROR: Invalid target specified: \"" ++ T.unpack selectedTarget ++ "\"" else
+    do
+      dbLoadStart <- getCurrentTime
+      (tdb, cdb, tcdb) <- loadDatabase
+      dbLoadEnd <- getCurrentTime
+
+      startTime <- getCurrentTime
+      (_, s) <- runBuild (runTarget (fromJust theTarget)) (BuildState buildArgs selectedTarget tdb cdb tcdb Set.empty Map.empty)
+      endTime <- getCurrentTime
+
+      dbSaveStart <- getCurrentTime
+      saveDatabase (getTargetTimestampDB s) (getChecksumDB s) (getTargetChecksumDB s)
+      dbSaveEnd <- getCurrentTime
+
+      putStrLn $ "DB load/save took " ++ show (diffUTCTime dbLoadEnd dbLoadStart) ++ "/" ++ show (diffUTCTime dbSaveEnd dbSaveStart) ++ " seconds."
+      putStrLn $ "Build took " ++ show (diffUTCTime endTime startTime) ++ " seconds."
+      return ()
+
+gatherAllTargetsInternal :: [Target] -> Set.Set Target -> Set.Set Target
+gatherAllTargetsInternal (t:ts) s =
+  let (recurse, newSet) = if Set.notMember t s then (True, Set.insert t s) else (False, s)
+  in if recurse then gatherAllTargetsInternal ts (gatherAllTargetsInternal (getDependencies t) newSet) else gatherAllTargetsInternal ts newSet
+gatherAllTargetsInternal [] s = s
+
+gatherAllTargets :: [Target] -> [Target]
+gatherAllTargets t =
+  let allTargets = Set.toList $ gatherAllTargetsInternal t Set.empty
+      targetsMinusInitial = L.filter (\x -> x /= head t) allTargets
+  in head t : targetsMinusInitial
+
+extractVarsFromArgs :: [String] -> ArgDict
+extractVarsFromArgs args = L.foldl' extractVarsFromArgsInternal Map.empty $ map (L.break (== '=')) args
+  where
+    extractVarsFromArgsInternal e (_, []) = e
+    extractVarsFromArgsInternal e (a, _:bs) = Map.insert a bs e
+
+-- | Returns the argument dictionary.
+getArgDict :: IO ArgDict
+getArgDict = do
+  args <- Env.getArgs
+  return $ extractVarsFromArgs args
+
+-- | Adds all of the variables in the execution environment into the
+-- argument dictionary. Allows for make-like variable passing.
+addEnvToDict :: ArgDict -> [(String, String)] -> IO ArgDict
+addEnvToDict m vars = do
+  env <- Env.getEnvironment
+  let valuesToAdd = map (\(x, y) -> (x, fromMaybe y $ L.lookup x env)) vars
+  return $ L.foldl' (\a (x, y) -> Map.insert x y a) m valuesToAdd
+
+removeVarsFromArgs :: [String] -> [String]
+removeVarsFromArgs args = L.foldl' removeVarsFromArgsInternal [] $ map (L.break (== '=')) args
+  where
+    removeVarsFromArgsInternal e (t, []) = e ++ [t]
+    removeVarsFromArgsInternal e (_, _:_) = e
+
+parseArgs :: [String] -> [Target] -> Int -> BuildArgs
+parseArgs args targets numJobs =
+  let cleanArgs = removeVarsFromArgs args
+      argsLen = length cleanArgs
+      target = if argsLen > 0 then T.pack.head $ cleanArgs else T.pack.show.head $ targets
+  in BuildArgs { buildTarget = target, maxBuildJobs = numJobs }
+
+-- | Makes a function that can be used to look up a value in the argument
+-- dictionary, returning a default value if the argument does not exist.
+makeArgDictLookupFunc :: String -> String -> ArgDict -> String
+makeArgDictLookupFunc arg defVal dict = fromMaybe defVal $ Map.lookup arg dict
+
+printSeparator :: IO ()
+printSeparator = putStrLn "============================================================"
+
+runBuild :: BuildM a -> BuildState -> IO (a, BuildState)
+runBuild m = runStateT (runBuildImpl m)
+
+loadDatabase :: IO (TargetTimestampDB, ChecksumDB, TargetChecksumDB)
+loadDatabase = do fileExists <- D.doesFileExist databaseFile
+                  fileContents <- if fileExists then B.readFile databaseFile else return B.empty
+                  return.handleEither $ Serialize.decode fileContents
+                  where handleEither (Left _) = (Map.empty, Map.empty, Map.empty)
+                        handleEither (Right (v, t, c, tc)) = if v == databaseVersion then (t, c, tc) else (Map.empty, Map.empty, Map.empty)
+
+saveDatabase :: TargetTimestampDB -> ChecksumDB -> TargetChecksumDB -> IO ()
+saveDatabase tdb cdb tcdb = B.writeFile databaseFile $ Serialize.encode (databaseVersion, tdb, cdb, tcdb)
+
+getCurrentTargetName :: BuildState -> T.Text
+getCurrentTargetName (BuildState _ t _ _ _ _ _) = t
+
+putCurrentTargetName :: BuildState -> T.Text -> BuildState
+putCurrentTargetName (BuildState a _ tdb cdb tcdb ts p) t = BuildState a t tdb cdb tcdb ts p
+
+getTargetTimestampDB :: BuildState -> TargetTimestampDB
+getTargetTimestampDB (BuildState _ _ tdb _ _ _ _) = tdb
+
+-- | Returns the 'TimestampDB' from the 'BuildState'
+getTimestampDB :: BuildState -> TimestampDB
+getTimestampDB (BuildState _ t tdb _ _ _ _) = Map.findWithDefault Map.empty t tdb
+
+-- | Puts the 'TimestampDB' back into the 'BuildState'
+putTimestampDB :: BuildState -> TimestampDB -> BuildState
+putTimestampDB (BuildState a t ftdb cdb tcdb ts p) tdb = BuildState a t (Map.insert t tdb ftdb) cdb tcdb ts p
+
+getChecksumDB :: BuildState -> ChecksumDB
+getChecksumDB (BuildState _ _ _ cdb _ _ _) = cdb
+
+putChecksumDB :: BuildState -> ChecksumDB -> BuildState
+putChecksumDB (BuildState a t tdb _ tcdb ts p) cdb = BuildState a t tdb cdb tcdb ts p
+
+getTargetChecksumDB :: BuildState -> TargetChecksumDB
+getTargetChecksumDB (BuildState _ _ _ _ tcdb _ _) = tcdb
+
+putTargetChecksumDB :: BuildState -> TargetChecksumDB -> BuildState
+putTargetChecksumDB (BuildState a t tdb cdb _ ts p) tcdb = BuildState a t tdb cdb tcdb ts p
+
+getUpToDateTargets :: BuildState -> UpToDateTargets
+getUpToDateTargets (BuildState _ _ _ _ _ ts _) = ts
+
+putUpToDateTargets :: BuildState -> UpToDateTargets -> BuildState
+putUpToDateTargets (BuildState a t tdb cdb tcdb _ p) ts = BuildState a t tdb cdb tcdb ts p
+
+getPendingDBUpdates :: BuildState -> PendingDBUpdates
+getPendingDBUpdates (BuildState _ _ _ _ _ _ p) = p
+
+putPendingDBUpdates :: BuildState -> PendingDBUpdates -> BuildState
+putPendingDBUpdates (BuildState a t tdb cdb tcdb ts _) = BuildState a t tdb cdb tcdb ts
+
+getMaxBuildJobs :: BuildState -> Int
+getMaxBuildJobs (BuildState a _ _ _ _ _ _) = maxBuildJobs a
+
+-- | Returns whether or not a target is up to date, based on the current build state.
+targetIsUpToDate :: BuildState -> Target -> Bool
+targetIsUpToDate (BuildState _ _ _ _ _ s _) t = Set.member t s
+
+-- | Partitions out up-to-date mappings
+partitionMappings :: [SrcTransform] -> [T.Text] -> Bool -> BuildM ([SrcTransform], [SrcTransform])
+partitionMappings files extraDeps force = do
+  s <- get
+  extraDepsChanged <- liftIO $ hasSrcChanged (getTimestampDB s) extraDeps
+  if force || extraDepsChanged then
+      return (files, [])
+    else do
+      shouldBuild <- liftIO $ mapM (shouldBuildMapping (getTimestampDB s) (getChecksumDB s)) files
+      let paired = zip shouldBuild files
+      let (a, b) = L.partition fst paired
+      return (map snd a, map snd b)
+
+(<||>) :: IO Bool -> IO Bool -> IO Bool
+(<||>) = liftM2 (||)
+
+-- function for filtering FileMappings based on them already being taken care of
+shouldBuildMapping :: TimestampDB -> ChecksumDB -> SrcTransform -> IO Bool
+shouldBuildMapping t c (OneToOne s d) = hasSrcChanged t [s] <||> hasChecksumChanged c [s] [d] <||> fmap not (D.doesFileExist $ T.unpack d)
+shouldBuildMapping t c (OneToMany s ds) = hasSrcChanged t [s] <||> hasChecksumChanged c [s] ds  <||> fmap (not.and) (mapM (D.doesFileExist.T.unpack) ds)
+shouldBuildMapping t c (ManyToOne ss d) = hasSrcChanged t ss <||> hasChecksumChanged c ss [d]  <||> fmap not (D.doesFileExist $ T.unpack d)
+shouldBuildMapping t c (ManyToMany ss ds) = hasSrcChanged t ss <||> hasChecksumChanged c ss ds  <||> fmap (not.and) (mapM (D.doesFileExist.T.unpack) ds)
+
+hasSrcChanged :: TimestampDB -> [T.Text] -> IO Bool
+hasSrcChanged m f = let filesInMap = zip f $ map (`Map.lookup` m) f
+                        checkTimeStamps _ (_, Nothing) = return True
+                        checkTimeStamps b (file, Just s) = getTimestamp file >>= (\t -> return $ b || (t /= s))
+                    in foldM checkTimeStamps False filesInMap
+
+getTimestamp :: T.Text -> IO Integer
+getTimestamp f = do
+  let unpackedFileName = T.unpack f
+  doesExist <- D.doesFileExist unpackedFileName
+  if doesExist then D.getModificationTime unpackedFileName >>= extractSeconds else return 0
+  where extractSeconds s = return $ (fromIntegral.fromEnum.utcTimeToPOSIXSeconds) s
+
+hasChecksumChanged :: ChecksumDB -> [T.Text] -> [T.Text] -> IO Bool
+hasChecksumChanged cdb s d = do
+  let (key, cs) = getChecksumPair s d
+  let mapVal = Map.lookup key cdb
+  return $ compareChecksums mapVal cs
+  where compareChecksums (Just mcs) ccs = mcs /= ccs
+        compareChecksums Nothing _ = True
+
+getChecksumPair :: [T.Text] -> [T.Text] -> (T.Text, Word32)
+getChecksumPair s d =
+  let joinedSrc = T.concat $ L.intersperse ":" s
+      joinedDest = T.concat $ L.intersperse ":" d
+  in (joinedDest, Hash.crc32 (TE.encodeUtf8 joinedSrc))
+
+buildFoldFunc :: Either [SrcTransform] T.Text -> Target -> BuildM (Either [SrcTransform] T.Text)
+buildFoldFunc (Left _) t@(Target name _ _ _ _) = do
+  buildState <- get
+  let oldTargetName = getCurrentTargetName buildState
+  put $ putCurrentTargetName buildState name
+  result <- runTarget t
+  newBuildState <- get
+  put $ putCurrentTargetName newBuildState oldTargetName
+  return result
+
+buildFoldFunc r@(Right _) _ = return r
+
+isLeft :: Either a b -> Bool
+isLeft (Left _) = True
+isLeft _ = False
+
+runTarget :: Target -> BuildM (Either [SrcTransform] T.Text)
+runTarget t@(Target name _ deps _ _) = do
+  buildState <- get
+  let outdatedTargets = filter (not.targetIsUpToDate buildState) deps
+  depStatus <- foldM buildFoldFunc (Left []) outdatedTargets
+  if isLeft depStatus then do
+      result <- runTargetInternal t
+      writePendingDBUpdates
+      return result
+    else
+      buildFailFunc depStatus name
+
+buildFailFunc :: Either [SrcTransform] T.Text -> T.Text -> BuildM (Either [SrcTransform] T.Text)
+buildFailFunc (Right err) name = do
+  liftIO printSeparator
+  liftIO $ putStr $ "ERROR: Error building target \"" ++ T.unpack name ++ "\": "
+  liftIO $ putStrLn $ T.unpack err
+  return $ Right ""
+buildFailFunc (Left _) _ = return $ Right ""
+
+runTargetInternal :: Target -> BuildM (Either [SrcTransform] T.Text)
+runTargetInternal t@(Target name hashFunc _ stages gatherers) = do
+  buildState <- get
+  let tcdb = getTargetChecksumDB buildState
+  let checksum = hashFunc t
+  let forceRebuild = checksum /= Map.findWithDefault 0 name tcdb
+  gatheredFiles <- liftIO $ runGatherers gatherers
+  let srcTransforms = map (flip OneToOne "") gatheredFiles
+  liftIO $ putStrLn $ "==== Target: \"" ++ T.unpack name ++ "\""
+  stageResult <- foldM stageFoldFunc (Left srcTransforms) $ zip stages $ repeat forceRebuild
+  if isLeft stageResult then targetSuccessFunc t else buildFailFunc stageResult name
+
+targetSuccessFunc :: Target -> BuildM (Either [SrcTransform] T.Text)
+targetSuccessFunc t@(Target name hashFunc _ _ _) = do
+  buildState <- get
+  let updatedTargets = Set.insert t $ getUpToDateTargets buildState
+  let updatedChecksums = Map.insert name (hashFunc t) $ getTargetChecksumDB buildState
+  put $ putTargetChecksumDB (putUpToDateTargets buildState updatedTargets) updatedChecksums
+  liftIO $ putStrLn $ "Successfully built target \"" ++ T.unpack name ++ "\""
+  liftIO $ putStrLn ""
+  return $ Left []
+
+stageFoldFunc :: Either [SrcTransform] T.Text -> (Stage, Bool) -> BuildM (Either [SrcTransform] T.Text)
+stageFoldFunc (Left t) (s, force) = runStage s force t
+stageFoldFunc r@(Right _) _ = return r
+
+workerThreadFunc :: (SrcTransform -> IO (Either SrcTransform T.Text)) -> MVar [SrcTransform] -> MVar (Either [SrcTransform] T.Text, [BuildM ()]) -> MVar (Either [SrcTransform] T.Text, [BuildM ()]) -> MVar Int -> IO ()
+workerThreadFunc sf q r f c = do
+  queue <- takeMVar q
+  if null queue then do
+      putMVar q queue
+      count <- takeMVar c
+      let newCount = count - 1
+      if newCount == 0 then do
+          putMVar c newCount
+          finalResult <- readMVar r
+          putMVar f finalResult
+          return ()
+        else do
+          putMVar c newCount
+          return ()
+    else do
+      let workItem = head queue
+      putMVar q (tail queue)
+      taskResult <- sf workItem
+      let dbThunk = updateDatabase taskResult workItem
+      resultAcc <- takeMVar r
+      let combine right@(Right _) _ = right
+          combine (Left ml) (Left v) = Left (v : ml)
+          combine (Left _) (Right v) = Right v
+      let newResultAcc = (\(res, thunks) -> (combine res taskResult, dbThunk : thunks)) resultAcc
+      putMVar r newResultAcc
+      workerThreadFunc sf q r f c
+
+stageHelper :: (SrcTransform -> IO (Either SrcTransform T.Text)) -> Int -> [SrcTransform] -> Either [SrcTransform] T.Text -> BuildM (Either [SrcTransform] T.Text)
+stageHelper f m i r = do
+  finalResultMVar <- liftIO newEmptyMVar
+  resultMVar <- liftIO $ newMVar (r, []) -- (overall result, database thunks)
+  queueMVar <- liftIO $ newMVar i
+  threadCountMVar <- liftIO $ newMVar m
+  if null i then
+      return r
+    else do
+      liftIO $ replicateM_ m (workerThreadFunc f queueMVar resultMVar finalResultMVar threadCountMVar)
+      result <- liftIO $ takeMVar finalResultMVar
+      sequence_ $ snd result
+      return $ fst result
+
+runStage :: Stage -> Bool -> [SrcTransform] -> BuildM (Either [SrcTransform] T.Text)
+runStage s@(Stage name _ _ extraDeps f) force m = do
+  liftIO $ putStrLn $ "-- Stage: \"" ++ T.unpack name ++ "\""
+  depScannedFiles <- liftIO $ processMappings s m
+  (targetsToBuild, upToDateTargets) <- partitionMappings depScannedFiles extraDeps force
+  bs <- get
+  result <- stageHelper f (getMaxBuildJobs bs) targetsToBuild (Left $ map transferUpToDateTarget upToDateTargets)
+  updateDatabaseExtraDeps result extraDeps
+
+-- These might not be quite correct. I guessed at what made sense.
+transferUpToDateTarget :: SrcTransform -> SrcTransform
+transferUpToDateTarget (OneToOne _ d) = OneToOne d ""
+transferUpToDateTarget (OneToMany _ ds) = ManyToOne ds ""
+transferUpToDateTarget (ManyToOne _ d) = OneToOne d ""
+transferUpToDateTarget (ManyToMany _ ds) = ManyToOne ds ""
+
+processMappings :: Stage -> [SrcTransform] -> IO [SrcTransform]
+processMappings (Stage _ t d _ _) m = do
+  let transMap = t m --transform input-only mappings into input -> output mappings
+  mapM d transMap
+
+updateDatabase :: Either l r -> SrcTransform -> BuildM ()
+updateDatabase (Right _) _ = return ()
+updateDatabase (Left _) (OneToOne s d) = updateDatabaseHelper [s] [d]
+updateDatabase (Left _) (OneToMany s ds) = updateDatabaseHelper [s] ds
+updateDatabase (Left _) (ManyToOne ss d) = updateDatabaseHelper ss [d]
+updateDatabase (Left _) (ManyToMany ss ds) = updateDatabaseHelper ss ds
+
+updateDatabaseHelper :: [T.Text] -> [T.Text] -> BuildM ()
+updateDatabaseHelper srcFiles destFiles = do
+  buildstate <- get
+  let pdbu = getPendingDBUpdates buildstate
+  timestamps <- liftIO $ mapM getTimestamp srcFiles
+  let filteredResults = filter (\(_, v) -> v /= 0) $ zip srcFiles timestamps
+  let updatedPDBU = L.foldl' (\m (k, v) -> Map.insert k v m) pdbu filteredResults
+  let cdb = getChecksumDB buildstate
+  let (key, cs) = getChecksumPair srcFiles destFiles
+  let updatedCDB = Map.insert key cs cdb
+  put $ putChecksumDB (putPendingDBUpdates buildstate updatedPDBU) updatedCDB
+  return ()
+
+updateDatabaseExtraDeps :: Either [SrcTransform] T.Text -> [T.Text] -> BuildM (Either [SrcTransform] T.Text)
+updateDatabaseExtraDeps result@(Right _) _ = return result
+updateDatabaseExtraDeps result@(Left _) deps = do
+  buildstate <- get
+  let pdbu = getPendingDBUpdates buildstate
+  timestamps <- liftIO $ mapM getTimestamp deps
+  let filteredResults = filter (\(_, v) -> v /= 0) $ zip deps timestamps
+  let updatedPDBU = L.foldl' (\m (k, v) -> Map.insert k v m) pdbu filteredResults
+  put $ putPendingDBUpdates buildstate updatedPDBU
+  return result
+
+writePendingDBUpdates :: BuildM ()
+writePendingDBUpdates = do
+  buildstate <- get
+  let tdb = getTimestampDB buildstate
+  let pdbu = getPendingDBUpdates buildstate
+  let updatedTDB = Map.union pdbu tdb
+  put $ putPendingDBUpdates (putTimestampDB buildstate updatedTDB) Map.empty
+  return ()
diff --git a/src/Dib/Builders/C.hs b/src/Dib/Builders/C.hs
new file mode 100644
--- /dev/null
+++ b/src/Dib/Builders/C.hs
@@ -0,0 +1,265 @@
+-- Copyright (c) 2010-2016 Brett Lajzer
+-- See LICENSE for license information.
+
+-- | A builder for C/C++ code.
+module Dib.Builders.C (
+  CTargetInfo(CTargetInfo, outputName, targetName, srcDir, outputLocation, compiler, linker, archiver, inFileOption, outFileOption, compileFlags, linkFlags, archiverFlags, includeDirs, extraCompileDeps, extraLinkDeps, exclusions, staticLibrary),
+  BuildLocation(InPlace, BuildDir, ObjAndBinDirs),
+  makeCTarget,
+  makeCleanTarget,
+  makeBuildDirs,
+  emptyConfig,
+  defaultGCCConfig,
+  defaultGXXConfig,
+  defaultClangConfig
+  ) where
+
+import Dib.Gatherers
+import Dib.Target
+import Dib.Types
+import Dib.Scanners.CDepScanner
+
+import Data.List as L
+import Data.Word
+import System.Process (system)
+import System.Directory as D
+import System.Exit
+import System.FilePath as F
+
+import qualified Data.Digest.CRC32 as Hash
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+
+-- | The record type that is used to pass configuration info for the C builder.
+data CTargetInfo = CTargetInfo {
+  -- | The name of the output file.
+  outputName :: T.Text,
+  -- | The name of the 'Target'. Should be unique among all 'Target's in a given build.
+  targetName :: T.Text,
+  -- | The directory containing the source for this target.
+  srcDir :: T.Text,
+  -- | A 'BuildLocation' that defines where the object and executable files go.
+  outputLocation :: BuildLocation,
+  -- | The compiler executable.
+  compiler :: T.Text,
+  -- | The linker executable.
+  linker :: T.Text,
+  -- | The archiver executable.
+  archiver :: T.Text,
+  -- | The command line option for the input file.
+  inFileOption :: T.Text,
+  -- | The command line option for the output file.
+  outFileOption :: T.Text,
+  -- | The compiler's include option
+  includeOption :: T.Text,
+  -- | Compiler flags.
+  compileFlags :: T.Text,
+  -- | Linker flags.
+  linkFlags :: T.Text,
+  -- | Archiver flags.
+  archiverFlags :: T.Text,
+  -- | A list of directories where include files can be found. Used for
+  -- dependency scanning and automatically appended to the compile line.
+  includeDirs :: [T.Text],
+  -- | Extra compilation dependencies.
+  extraCompileDeps :: [T.Text],
+  -- | Extra linking dependencies.
+  extraLinkDeps :: [T.Text],
+  -- | Files to exclude from the build.
+  exclusions :: [T.Text],
+  -- | Whether or not to build a static lib (using the archiver)
+  staticLibrary :: Bool
+  }
+
+-- | Given a 'CTargetInfo' and a 'Target', produces a checksum
+cTargetHash :: CTargetInfo -> Target -> Word32
+cTargetHash info _ =
+  let textHash = TE.encodeUtf8 $ T.intercalate "^" [
+        "srcDir",
+        srcDir info,
+        "compiler",
+        compiler info,
+        "linker",
+        linker info,
+        "archiver",
+        archiver info,
+        "inFileOption",
+        inFileOption info,
+        "outFileOption",
+        outFileOption info,
+        "includeOption",
+        includeOption info,
+        "compileFlags",
+        compileFlags info,
+        "linkFlags",
+        linkFlags info,
+        "archiverFlags",
+        archiverFlags info,
+        "includeDirs",
+        T.intercalate "^^" $ includeDirs info,
+        "extraCompileDeps",
+        T.intercalate "^^" $ extraCompileDeps info,
+        "extraLinkDeps",
+        T.intercalate "^^" $ extraLinkDeps info,
+        "exclusions",
+        T.intercalate "^^" $ exclusions info,
+        "staticLibrary",
+        if staticLibrary info then "True" else "False"
+        ]
+
+  in Hash.crc32 textHash
+
+-- | The data type for specifying where built files end up.
+data BuildLocation =
+  -- | Specifies that object files will end up adjacent to their source files
+  -- and the executable will be in the same directory as the dib.hs file.
+  InPlace
+  -- | Specifies that the object files and executable will go in a certain directory.
+  | BuildDir T.Text
+  -- | Specifies that the object files will go in the first directory and the
+  -- executable in the second directory.
+  | ObjAndBinDirs T.Text T.Text
+
+-- | An empty configuration.
+emptyConfig :: CTargetInfo
+emptyConfig = CTargetInfo {
+  outputName = "",
+  targetName = "",
+  srcDir = "",
+  outputLocation = InPlace,
+  compiler = "",
+  linker = "",
+  archiver = "",
+  inFileOption = "",
+  outFileOption = "",
+  includeOption = "",
+  compileFlags = "",
+  linkFlags = "",
+  archiverFlags = "",
+  includeDirs = [],
+  extraCompileDeps = [],
+  extraLinkDeps = [],
+  exclusions = [],
+  staticLibrary = False
+  }
+
+-- | A default configuration for gcc.
+defaultGCCConfig :: CTargetInfo
+defaultGCCConfig = emptyConfig {
+  compiler = "gcc",
+  linker = "gcc",
+  archiver = "ar",
+  inFileOption = "-c",
+  outFileOption = "-o",
+  includeOption = "-I",
+  archiverFlags = "rs"
+  }
+
+-- | A default configuration for g++.
+defaultGXXConfig :: CTargetInfo
+defaultGXXConfig = defaultGCCConfig {
+  compiler = "g++",
+  linker = "g++"
+  }
+
+-- | A default configuration for clang.
+defaultClangConfig :: CTargetInfo
+defaultClangConfig = defaultGCCConfig {
+  compiler = "clang",
+  linker = "clang"
+  }
+
+massageFilePath :: T.Text -> T.Text
+massageFilePath p = T.replace "\\" "_" $ T.replace "/" "_" p
+
+remapObjFile :: BuildLocation -> T.Text -> T.Text
+remapObjFile InPlace f = f
+remapObjFile (BuildDir d) f = d `T.snoc` F.pathSeparator `T.append` massageFilePath f
+remapObjFile (ObjAndBinDirs d _) f = d `T.snoc` F.pathSeparator `T.append` massageFilePath f
+
+remapBinFile :: BuildLocation -> T.Text -> T.Text
+remapBinFile InPlace f = f
+remapBinFile (BuildDir d) f = d `T.snoc` F.pathSeparator `T.append` f
+remapBinFile (ObjAndBinDirs _ d) f = d `T.snoc` F.pathSeparator `T.append` f
+
+-- | Given a 'CTargetInfo', will make the directories required to build the project.
+makeBuildDirs :: CTargetInfo -> IO ()
+makeBuildDirs info = do
+  let helper InPlace = return ()
+      helper (BuildDir d) = D.createDirectoryIfMissing True (T.unpack d)
+      helper (ObjAndBinDirs d d2) = D.createDirectoryIfMissing True (T.unpack d) >> D.createDirectoryIfMissing True (T.unpack d2)
+  helper (outputLocation info)
+  return ()
+
+excludeFiles :: [T.Text] -> T.Text -> Bool
+excludeFiles excl file = L.any (`T.isSuffixOf` file) excl
+
+-- | Given a 'CTargetInfo', produces a 'Target'
+makeCTarget :: CTargetInfo -> Target
+makeCTarget info =
+  let includeDirString = includeOption info `T.append` T.intercalate (" " `T.append` includeOption info) (includeDirs info)
+      makeBuildString s t = T.unpack $ T.concat [compiler info, " ", inFileOption info, " ", s, " ", outFileOption info, " ", t, " ", includeDirString, " ", compileFlags info]
+      makeLinkString ss t = T.unpack $ T.concat [linker info, " ", T.unwords ss, " ", outFileOption info, " ", t, " ", linkFlags info]
+      makeArchiveString ss t = T.unpack $ T.concat [archiver info, " ", archiverFlags info, " ", t, " ", T.unwords ss]
+
+      buildCmd (ManyToOne ss t) = do
+        let sourceFile = head ss
+        let buildString = makeBuildString sourceFile t
+        putStrLn $ "Building: " ++ T.unpack sourceFile
+        exitCode <- system buildString
+        handleExitCode exitCode t buildString
+      buildCmd _ = return $ Right "Unhandled SrcTransform."
+
+      linkCmd (ManyToOne ss t) = do
+        let linkString = makeLinkString ss t
+        putStrLn $ "Linking: " ++ T.unpack t
+        exitCode <- system linkString
+        handleExitCode exitCode t linkString
+      linkCmd _ = return $ Right "Unhandled SrcTransform."
+
+      archiveCmd (ManyToOne ss t) = do
+        let archiveString = makeArchiveString ss t
+        putStrLn $ "Archiving: " ++ T.unpack t
+        exitCode <- system archiveString
+        handleExitCode exitCode t archiveString
+      archiveCmd _ = return $ Right "Unhandled SrcTransform."
+
+      buildDirGatherer = makeCommandGatherer $ makeBuildDirs info
+      cppStage = Stage "compile" (map (changeExt "o" (outputLocation info))) (cDepScanner (map T.unpack $ includeDirs info)) (extraCompileDeps info) buildCmd
+      linkStage = Stage "link" (combineTransforms (remapBinFile (outputLocation info) $ outputName info)) return (extraLinkDeps info) linkCmd
+      archiveStage = Stage "archive" (combineTransforms (remapBinFile (outputLocation info) $ outputName info)) return [] archiveCmd
+  in Target (targetName info) (cTargetHash info) [] [cppStage, if staticLibrary info then archiveStage else linkStage] [buildDirGatherer, makeFileTreeGatherer (srcDir info) (matchExtensionsExcluded [".cpp", ".c"] [excludeFiles $ exclusions info])]
+
+changeExt :: T.Text -> BuildLocation -> SrcTransform -> SrcTransform
+changeExt newExt b (OneToOne l _) = OneToOne l $ remapObjFile b $ T.append (T.dropWhileEnd (/='.') l) newExt
+changeExt _ _ _ = undefined
+
+handleExitCode :: ExitCode -> T.Text -> String -> IO (Either SrcTransform T.Text)
+handleExitCode ExitSuccess t _ = return $ Left $ OneToOne t ""
+handleExitCode (ExitFailure _) _ e = return $ Right $ T.pack (show e)
+
+combineTransforms :: T.Text -> [SrcTransform] -> [SrcTransform]
+combineTransforms t st = [ManyToOne sources t]
+  where sources = foldl' (\l (OneToOne s _) -> l ++ [s]) [] st
+
+-- | Given a 'CTargetInfo', produces a 'Target' that will clean the project.
+makeCleanTarget :: CTargetInfo -> Target
+makeCleanTarget info =
+  let cleanCmd (OneToOne s _) = do
+        putStrLn $ "removing: " ++ T.unpack s
+        D.removeFile (T.unpack s)
+        return $ Left $ OneToOne "" ""
+      cleanCmd _ = error "Should never hit this."
+
+      objDir InPlace = srcDir info
+      objDir (BuildDir d) = d
+      objDir (ObjAndBinDirs d _) = d
+
+      programFile InPlace = outputName info
+      programFile (BuildDir d) = d `T.snoc` F.pathSeparator `T.append` outputName info
+      programFile (ObjAndBinDirs _ d) = d `T.snoc` F.pathSeparator `T.append` outputName info
+
+      cleanStage = Stage "clean" id return [] cleanCmd
+      objectGatherer = makeFileTreeGatherer (objDir $ outputLocation info) (matchExtension ".o")
+      programGatherer = makeSingleFileGatherer (programFile $ outputLocation info)
+  in Target ("clean-" `T.append` targetName info) (const 0) [] [cleanStage] [objectGatherer, programGatherer]
diff --git a/src/Dib/Builders/Copy.hs b/src/Dib/Builders/Copy.hs
new file mode 100644
--- /dev/null
+++ b/src/Dib/Builders/Copy.hs
@@ -0,0 +1,35 @@
+-- Copyright (c) 2010-2016 Brett Lajzer
+-- See LICENSE for license information.
+
+-- | A trivial builder that copies a directory tree from one location to another.
+module Dib.Builders.Copy (
+  makeCopyTarget
+  ) where
+
+import Dib.Gatherers
+import Dib.Types
+
+import qualified Data.Text as T
+import qualified System.Directory as D
+import System.FilePath as P
+
+copyFunc :: SrcTransform -> IO (Either SrcTransform T.Text)
+copyFunc (OneToOne s t) = do
+  let unpackedTarget = T.unpack t
+  let unpackedSource = T.unpack s
+  D.createDirectoryIfMissing True $ takeDirectory unpackedTarget
+  putStrLn $ "Copying: " ++ unpackedSource ++ " -> " ++ unpackedTarget
+  D.copyFile unpackedSource unpackedTarget
+  return $ Left (OneToOne t "")
+copyFunc _ = return $ Right "Unexpected SrcTransform"
+
+remapFile :: String -> String -> SrcTransform -> SrcTransform
+remapFile src dest (OneToOne s _) = OneToOne s $ T.pack $ dest </> makeRelative src (T.unpack s)
+remapFile _ _ _ = error "Unhandled SrcTransform"
+
+-- | The 'makeCopyTarget' function makes a target that copies a directory tree.
+-- It takes a name, a source directory, destination directory, and gather filter.
+makeCopyTarget :: T.Text -> T.Text -> T.Text -> FilterFunc -> [T.Text] -> Target
+makeCopyTarget name src dest f extraDeps =
+  let stage = Stage "copy" (map $ remapFile (T.unpack src) (T.unpack dest)) return extraDeps copyFunc
+  in Target name (const 0) [] [stage] [makeFileTreeGatherer src f]
diff --git a/src/Dib/Builders/Simple.hs b/src/Dib/Builders/Simple.hs
new file mode 100644
--- /dev/null
+++ b/src/Dib/Builders/Simple.hs
@@ -0,0 +1,49 @@
+-- Copyright (c) 2010-2016 Brett Lajzer
+-- See LICENSE for license information.
+
+-- | The Simple builder allows you to execute a 'OneToOne' trasformation
+-- given a function that generates a command line from the source and
+-- target files.
+module Dib.Builders.Simple (
+  makeSimpleTarget
+  ) where
+
+import Dib.Gatherers
+import Dib.Types
+
+import qualified Data.Text as T
+import qualified System.Directory as D
+import System.Process (system)
+import System.Exit
+import System.FilePath as P
+
+buildFunc :: (String -> String -> String) -> SrcTransform -> IO (Either SrcTransform T.Text)
+buildFunc func (OneToOne s t) = do
+  let unpackedTarget = T.unpack t
+  let unpackedSource = T.unpack s
+  D.createDirectoryIfMissing True $ takeDirectory unpackedTarget
+  putStrLn $ "Building: " ++ unpackedSource ++ " -> " ++ unpackedTarget
+  let buildCmd = func unpackedSource unpackedTarget
+  exitCode <- system buildCmd
+  handleExitCode exitCode t buildCmd
+buildFunc _ _ = return $ Right "Unexpected SrcTransform"
+
+remapFile :: String -> String -> T.Text -> SrcTransform -> SrcTransform
+remapFile src dest ext (OneToOne s _) = OneToOne s $ T.pack $ dest </> makeRelative src (T.unpack (changeExt s ext))
+remapFile _ _ _ _ = error "Unhandled SrcTransform"
+
+changeExt :: T.Text -> T.Text -> T.Text
+changeExt path = T.append (T.dropWhileEnd (/='.') path)
+
+--TODO: move to a utility module and factor out of C builder
+handleExitCode :: ExitCode -> T.Text -> String -> IO (Either SrcTransform T.Text)
+handleExitCode ExitSuccess t _ = return $ Left $ OneToOne t ""
+handleExitCode (ExitFailure _) _ e = return $ Right $ T.pack (show e)
+
+-- | The 'makeSimpleTarget' function generates a target.
+-- It takes a name, source directory, destination directory, destination extension,
+-- gather filter, and a function to build the command line.
+makeSimpleTarget :: T.Text -> T.Text -> T.Text -> T.Text -> FilterFunc -> [T.Text] -> (String -> String -> String) -> Target
+makeSimpleTarget name src dest ext f extraDeps buildCmdBuilder =
+  let stage = Stage name (map $ remapFile (T.unpack src) (T.unpack dest) ext) return extraDeps (buildFunc buildCmdBuilder)
+  in Target name (const 0) [] [stage] [makeFileTreeGatherer src f]
diff --git a/src/Dib/Gatherers.hs b/src/Dib/Gatherers.hs
new file mode 100644
--- /dev/null
+++ b/src/Dib/Gatherers.hs
@@ -0,0 +1,141 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+-- Copyright (c) 2010-2016 Brett Lajzer
+-- See LICENSE for license information.
+
+-- | Module that exposes all of the various 'GatherStrategy' types and functions
+-- for dealing with them.
+module Dib.Gatherers(
+  SingleFileGatherer(),
+  DirectoryGatherer(),
+  FileTreeGatherer(),
+  Gatherer(),
+  CommandGatherer(),
+  wrapGatherStrategy,
+  runGatherers,
+  makeSingleFileGatherer,
+  makeDirectoryGatherer,
+  makeFileTreeGatherer,
+  makeCommandGatherer,
+  matchAll,
+  matchExtension,
+  matchExtensionExcluded,
+  matchExtensions,
+  matchExtensionsExcluded
+  )where
+
+import Control.Monad
+import Dib.Types
+import Data.List
+import qualified Data.Text as T
+import qualified System.Directory as D
+import System.FilePath
+
+instance GatherStrategy SingleFileGatherer where
+  gather = singleFileGatherFunc
+
+instance GatherStrategy DirectoryGatherer where
+  gather = directoryGathererFunc
+
+instance GatherStrategy FileTreeGatherer where
+  gather = fileTreeGatherFunc
+
+instance GatherStrategy CommandGatherer where
+  gather = commandGatherFunc
+
+-- | Runs a list of 'Gatherer's and returns the concatenation of their output.
+runGatherers :: [Gatherer] -> IO [T.Text]
+runGatherers gs = mapM (\(Gatherer s) -> gather s) gs >>= \x -> foldM (\a b -> return (a ++ b)) [] x
+
+-- | Convenience function to turn a 'GatherStrategy' into a 'Gatherer'.
+wrapGatherStrategy :: GatherStrategy s => s -> Gatherer
+wrapGatherStrategy = Gatherer
+
+-- | Constructs a 'Gatherer' that returns a single file.
+makeSingleFileGatherer :: T.Text -> Gatherer
+makeSingleFileGatherer = wrapGatherStrategy.SingleFileGatherer
+
+-- | Constructs a 'Gatherer' that returns all files in a directory (but not its
+-- subdirectories) that match a given filter.
+makeDirectoryGatherer :: T.Text -> FilterFunc -> Gatherer
+makeDirectoryGatherer d = wrapGatherStrategy.DirectoryGatherer d
+
+-- | Constructs a 'Gatherer' that returns all files in a directory tree that
+-- match a given filter.
+makeFileTreeGatherer :: T.Text -> FilterFunc -> Gatherer
+makeFileTreeGatherer d = wrapGatherStrategy.FileTreeGatherer d
+
+-- | Constructs a 'Gatherer' that runs an arbitrary 'IO' action.
+makeCommandGatherer :: IO () -> Gatherer
+makeCommandGatherer = wrapGatherStrategy.CommandGatherer
+
+singleFileGatherFunc :: SingleFileGatherer -> IO [T.Text]
+singleFileGatherFunc (SingleFileGatherer f) = do
+  exists <- D.doesFileExist $ T.unpack f
+  return [f | exists]
+
+directoryGathererFunc :: DirectoryGatherer -> IO [T.Text]
+directoryGathererFunc (DirectoryGatherer d f) = do
+  let unpackedDir = T.unpack d
+  contents <- D.getDirectoryContents unpackedDir
+  filtContents <- mapM filePathDeterminer $ fixFilePaths unpackedDir $ filePathFilter contents
+  let (_, files) = directorySplitter filtContents
+  return $ filter f $ map T.pack files
+
+fileTreeGatherFunc :: FileTreeGatherer -> IO [T.Text]
+fileTreeGatherFunc (FileTreeGatherer d f) = do
+  files <- rGetFilesInDir $ T.unpack d
+  return $ filter f $ map T.pack files
+
+commandGatherFunc :: CommandGatherer -> IO [T.Text]
+commandGatherFunc (CommandGatherer f) = do
+  f
+  return []
+
+-- functions to handle recursively spidering a directory
+filePathDeterminer :: FilePath -> IO (FilePath, Bool)
+filePathDeterminer f = D.doesDirectoryExist f >>= \d -> return (f, d)
+
+filePathFilter :: [FilePath] -> [FilePath]
+filePathFilter = filter noSpecialOrHiddenDirs
+    where noSpecialOrHiddenDirs (x:_) = x /= '.'
+          noSpecialOrHiddenDirs [] = False
+
+directorySplitter :: [(FilePath, Bool)] -> ([FilePath], [FilePath])
+directorySplitter = foldl' splitter ([], [])
+    where splitter (d, f) (path, dir) = if dir then (d ++ [path], f) else (d, f ++ [path])
+
+fixFilePaths :: FilePath -> [FilePath] -> [FilePath]
+fixFilePaths root = map (root </>)
+
+-- | Recursively gets all files in the given directory and its subdirectories.
+rGetFilesInDir :: FilePath -> IO [FilePath]
+rGetFilesInDir dir = do
+    contents <- D.getDirectoryContents dir
+    filtContents <- mapM filePathDeterminer $ fixFilePaths dir $ filePathFilter contents
+    let (dirs, files) = directorySplitter filtContents
+    spideredDirs <- mapM rGetFilesInDir dirs
+    return $ concat spideredDirs ++ files
+
+-- | Filter function that returns all files.
+matchAll :: FilterFunc
+matchAll _ = True
+
+-- | Filter function that returns files with a given extension.
+matchExtension :: T.Text -> FilterFunc
+matchExtension = T.isSuffixOf
+
+-- | Filter function that returns files that match any of a list of extensions.
+matchExtensions :: [T.Text] -> FilterFunc
+matchExtensions exts file = foldl' foldFunc False exts
+  where foldFunc True _ = True
+        foldFunc False e = e `T.isSuffixOf` file
+
+-- | Filter function that returns files with a given extension that don't match exclusion rules.
+matchExtensionExcluded :: T.Text -> [T.Text -> Bool] -> FilterFunc
+matchExtensionExcluded ext rules file = T.isSuffixOf ext file && (not.or $ map (\r -> r file) rules)
+
+-- | Filter function that returns files that match any of a list of extensions, but don't match the exclusion rules.
+matchExtensionsExcluded :: [T.Text] -> [T.Text -> Bool] -> FilterFunc
+matchExtensionsExcluded exts rules file = foldl' foldFunc False exts
+  where foldFunc True _ = True
+        foldFunc False e = (e `T.isSuffixOf` file) && (not.or $ map (\r -> r file) rules)
diff --git a/src/Dib/Scanners/CDepScanner.hs b/src/Dib/Scanners/CDepScanner.hs
new file mode 100644
--- /dev/null
+++ b/src/Dib/Scanners/CDepScanner.hs
@@ -0,0 +1,152 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving, ExistentialQuantification, KindSignatures, FlexibleContexts #-}
+-- Copyright (c) 2010-2016 Brett Lajzer
+-- See LICENSE for license information.
+
+-- | C dependency scanner. Runs a stripped-down pre-processor to scan for
+-- include files (recursively).
+module Dib.Scanners.CDepScanner (
+  cDepScanner
+  ) where
+
+import Dib.Types
+import qualified Data.Set as S
+import qualified Data.Text as T
+import qualified System.Directory as Dir
+import qualified System.FilePath as F
+import Control.Monad.State.Lazy
+
+data Dependency = Dependency String String
+  deriving (Show)
+
+instance Eq Dependency where
+  (Dependency f1 _) == (Dependency f2 _) = f1 == f2
+
+instance Ord Dependency where
+  compare (Dependency f1 _) (Dependency f2 _) = compare f1 f2
+
+getPathFromDep :: Dependency -> String
+getPathFromDep (Dependency _ path) = path
+
+--    already read includes, list of include paths
+data ParseState = PS {
+  currentDeps :: S.Set Dependency,
+  searchPaths :: [String] }
+  deriving (Show)
+
+newtype DepGatherer a = DepGatherer {
+  runDepGatherer :: StateT ParseState IO a
+  } deriving (Functor, Applicative, Monad, MonadIO, MonadState ParseState)
+
+removeCR :: String -> String
+removeCR = filter (/= '\r')
+
+removeLeadingWS :: String -> String
+removeLeadingWS = dropWhile (\x -> x == ' ' || x == '\t')
+
+removeBlockComment :: String -> String
+removeBlockComment ('*':'/':xs) = removeComments xs
+removeBlockComment (_:xs) = removeBlockComment xs
+removeBlockComment [] = error "Unterminated block comment."
+
+removeLineComment :: String -> String
+removeLineComment ('\n':xs) = removeComments xs
+removeLineComment (_:xs) = removeLineComment xs
+removeLineComment [] = []
+
+processCharLiteral :: String -> String
+processCharLiteral ('\\':x:'\'':xs) = '\\' : x : '\'' : removeComments xs
+processCharLiteral (x:'\'':xs) = x : '\'' : removeComments xs
+processCharLiteral (x:xs) = x : removeComments xs
+processCharLiteral [] = []
+
+processStringLiteral :: String -> String
+processStringLiteral ('\\':'"':xs) = '\\' : '"' : processStringLiteral xs
+processStringLiteral ('"':xs) = '"' : removeComments xs
+processStringLiteral (x:xs) = x : processStringLiteral xs
+processStringLiteral [] = error "Unterminated string literal."
+
+processDirective :: String -> String
+processDirective ('\\':'\n':xs) = '\\' : '\n' : processDirective xs
+processDirective ('\n':xs) = '\n' : removeComments xs
+processDirective (x:xs) = x : processDirective xs
+processDirective [] = []
+
+removeComments :: String -> String
+removeComments ('#':xs) = '#' : processDirective xs
+removeComments ('/':'*':xs) = removeBlockComment xs
+removeComments ('/':'/':xs) = removeLineComment xs
+removeComments ('\'':xs) = '\'' : processCharLiteral xs
+removeComments ('"':xs) = '"' : processStringLiteral xs
+removeComments (x:xs) = x : removeComments xs
+removeComments [] = []
+
+filterBlank :: [String] -> [String]
+filterBlank = filter (\x -> x /= "\n" && x /= [])
+
+extractIncludes :: [String] -> [String]
+extractIncludes = filter (\x -> "#include" == takeWhile (/= ' ') x)
+
+dequoteInclude :: String -> String
+dequoteInclude s =
+  let endPortion = dropWhile (\x -> x /= '\"' && x /= '<') s
+      endLen = length endPortion
+  in if endLen > 0 then takeWhile (\x -> x /= '\"' && x /= '>') $ tail endPortion else []
+
+-- intial pass, removes comments and leading whitespace, then filters out extra lines
+pass1 :: String -> [String]
+pass1 s = filterBlank $ map removeLeadingWS $ lines $ removeComments (removeCR s)
+
+-- second pass, cleans up includes
+pass2 :: [String] -> [String]
+pass2 l = filterBlank $ map dequoteInclude $ extractIncludes l
+
+gatherDependencies :: String -> [String]
+gatherDependencies = pass2.pass1
+
+possibleFilenames :: FilePath -> [FilePath] -> [FilePath]
+possibleFilenames file = map (\p -> F.normalise $ F.combine p file)
+
+pathToDependency :: FilePath -> Dependency
+pathToDependency path = Dependency (F.takeFileName path) path
+
+spider :: forall (m :: * -> *).(MonadIO m, MonadState ParseState m) => String -> m ()
+spider file = do
+  s <- get
+  paths <- filterM (includeFilter $ currentDeps s) $ possibleFilenames file (searchPaths s)
+  spiderHelper paths
+  return ()
+    where
+      includeFilter deps f = do
+        exists <- liftIO $ Dir.doesFileExist f
+        return $ exists && not (S.member (pathToDependency f) deps)
+
+spiderHelper :: forall (m :: * -> *).(MonadIO m, MonadState ParseState m) => [FilePath] -> m ()
+spiderHelper [] =  return ()
+spiderHelper (file:_) = do
+  c <- liftIO $ readFile file
+  let deps = gatherDependencies c
+  s <- get
+  put $ s {currentDeps = S.insert (pathToDependency file) (currentDeps s) }
+  mapM_ spider deps
+  return ()
+
+spiderLauncher :: forall (m :: * -> *).(MonadIO m, MonadState ParseState m) => FilePath -> m ()
+spiderLauncher file = do
+  c <- liftIO $ readFile file
+  let deps = gatherDependencies c
+  mapM_ spider deps
+  return ()
+
+getDepsForFile :: [FilePath] -> FilePath -> IO [T.Text]
+getDepsForFile includeDirs file = do
+  (_, s) <- runStateT (runDepGatherer $ spiderLauncher file) PS {currentDeps=S.empty, searchPaths=[F.dropFileName file, "."] ++ includeDirs }
+  return $ map (T.pack.getPathFromDep) (S.toList (currentDeps s))
+
+-- | Takes in a list of include directories, extra dependencies, a 'SrcTransform',
+-- and returns a new 'SrcTransform' with the dependencies injected into the source
+-- side.
+cDepScanner :: [FilePath] -> SrcTransform -> IO SrcTransform
+cDepScanner includeDirs (OneToOne i o) = getDepsForFile includeDirs (T.unpack i) >>= \d -> return $ ManyToOne (i:d) o
+cDepScanner includeDirs (OneToMany i o) = getDepsForFile includeDirs (T.unpack i) >>= \d -> return $ ManyToMany (i:d) o
+cDepScanner _ _ = error "Unimplemented. Implement this if it is a valid relationship."
+
diff --git a/src/Dib/Stage.hs b/src/Dib/Stage.hs
new file mode 100644
--- /dev/null
+++ b/src/Dib/Stage.hs
@@ -0,0 +1,18 @@
+-- Copyright (c) 2010-2016 Brett Lajzer
+-- See LICENSE for license information.
+
+-- | Module exposing the 'Stage' type and related type wrappers, along with a
+-- convenience 'emptyStage'.
+module Dib.Stage(
+  Stage(Stage),
+  InputTransformer,
+  DepScanner,
+  StageFunc,
+  emptyStage
+  ) where
+
+import Dib.Types
+
+-- | A stage that does nothing and just passes the 'SrcTransform's through.
+emptyStage :: Stage
+emptyStage = Stage "empty" id return [] (return.Left)
diff --git a/src/Dib/Target.hs b/src/Dib/Target.hs
new file mode 100644
--- /dev/null
+++ b/src/Dib/Target.hs
@@ -0,0 +1,48 @@
+-- Copyright (c) 2010-2016 Brett Lajzer
+-- See LICENSE for license information.
+
+-- | Module that exposes the 'Target' data type and a handful of convenience functions
+-- for dealing with 'Target's.
+module Dib.Target(
+  Target(Target),
+  addDependency,
+  addDependencies,
+  getDependencies,
+  makePhonyTarget,
+  makeCommandTarget,
+  targetDepChecksum
+  ) where
+
+import Dib.Types
+import Dib.Gatherers
+
+import Data.Word
+
+import qualified Data.Digest.CRC32 as Hash
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
+
+-- | Adds a dependency on another 'Target'.
+addDependency :: Target -> Target -> Target
+addDependency (Target name checksum deps stages gatherers) newDep = Target name checksum (newDep : deps) stages gatherers
+
+-- | Adds a dependency on a list of 'Target's.
+addDependencies :: Target -> [Target] -> Target
+addDependencies (Target name checksum deps stages gatherers) newDeps = Target name checksum (newDeps ++ deps) stages gatherers
+
+-- | Gets dependencies of a 'Target'.
+getDependencies :: Target -> [Target]
+getDependencies (Target _ _ deps _ _) = deps
+
+-- | Makes a 'Target' that doesn't build anything but can be used as a meta
+-- 'Target', i.e. the "all" target in make.
+makePhonyTarget :: T.Text -> [Target] -> Target
+makePhonyTarget name deps = Target name (const 0) deps [] []
+
+-- | Makes a 'Target' that runs an arbitrary 'IO' action.
+makeCommandTarget :: T.Text -> [Target] -> IO () -> Target
+makeCommandTarget name deps command = Target name (const 0) deps [] [makeCommandGatherer command]
+
+-- | Computes a checksum from the direct dependencies of a target
+targetDepChecksum :: Target -> Word32
+targetDepChecksum (Target _ _ deps _ _) = Hash.crc32 $ TE.encodeUtf8 $ T.intercalate "^" $ map (\(Target n _ _ _ _) -> n) deps
diff --git a/src/Dib/Types.hs b/src/Dib/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Dib/Types.hs
@@ -0,0 +1,131 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving, ExistentialQuantification, OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# OPTIONS_HADDOCK hide, prune #-}
+
+-- Copyright (c) 2010-2016 Brett Lajzer
+-- See LICENSE for license information.
+
+-- | Various types used in dib. Due to certain circumstances, this module does
+-- not export any of these types directly; other modules do.
+module Dib.Types where
+
+import Control.Monad.State as S
+import qualified Data.Map as Map
+import qualified Data.Serialize as Serialize
+import qualified Data.Set as Set
+import qualified Data.Text as T
+import Data.Word
+
+-- | Type wrapper for the timestamp database.
+type TimestampDB = Map.Map T.Text Integer
+-- | Type wrapper for the target timestamp database.
+type TargetTimestampDB = Map.Map T.Text TimestampDB
+-- | Type wrapper for the checksum database.
+type ChecksumDB = Map.Map T.Text Word32
+-- | Type wrapper for the target checksum database.
+type TargetChecksumDB = Map.Map T.Text Word32
+-- | Type wrapper for the set of currently up-to-date 'Target's.
+type UpToDateTargets = Set.Set Target
+-- | Type wrapper for the list of database updates that will happen after
+-- a successful 'Target' build.
+type PendingDBUpdates = Map.Map T.Text Integer
+-- | Type wrapper for the dictionary of arguments that are extracted from the
+-- command line.
+type ArgDict = Map.Map String String
+
+-- | Internal type that contains information state used by the build.
+data BuildState = BuildState BuildArgs T.Text TargetTimestampDB ChecksumDB TargetChecksumDB UpToDateTargets PendingDBUpdates
+
+-- | Configuration arguments used by the build
+data BuildArgs = BuildArgs {
+  -- | The target to build.
+  buildTarget :: T.Text,
+  -- | The maximum number of build jobs to spawn.
+  maxBuildJobs :: Int
+  }
+
+-- | Newtype wrapper for the build monad transformer stack.
+newtype BuildM a = BuildMImpl {
+  runBuildImpl :: S.StateT BuildState IO a
+  } deriving (Functor, Applicative, Monad, MonadIO, MonadState BuildState)
+
+-- | Data type for expressing mapping of input files to output files
+data SrcTransform =
+  -- | One input to one output file.
+  OneToOne T.Text T.Text
+  -- | One input file to many output files.
+  | OneToMany T.Text [T.Text]
+  -- | Many input files to one output file.
+  | ManyToOne [T.Text] T.Text
+  -- | Many input to many output files.
+  | ManyToMany [T.Text] [T.Text]
+  deriving (Show)
+
+-- | Type wrapper for functions transforming a list of 'SrcTransform's into a
+-- list of 'SrcTransform's suitable for passing into the 'DepScanner'.
+type InputTransformer = ([SrcTransform] -> [SrcTransform])
+
+-- | Type wrapper for a function that takes a 'SrcTransform' and produces
+-- a 'SrcTransform' with dependencies included in the input.
+type DepScanner = (SrcTransform -> IO SrcTransform)
+
+-- | Type wrapper for a function that given a 'SrcTransform', produces either
+-- a 'SrcTransform' containing the output files in the input, or an error.
+type StageFunc = (SrcTransform -> IO (Either SrcTransform T.Text))
+
+-- | Type describing a build stage.
+-- Takes a name, an 'InputTransformer', 'DepScanner', additional dependencies, and the builder function.
+data Stage = Stage T.Text InputTransformer DepScanner [T.Text] StageFunc
+
+-- | Type wrapper for a function that given a Target produces a checksum.
+type ChecksumFunc = (Target -> Word32)
+
+-- | Describes a build target.
+-- Takes a name, checksum function, list of dependencies, list of 'Stage's, and a list of 'Gatherer's.
+data Target = Target T.Text ChecksumFunc [Target] [Stage] [Gatherer]
+
+instance Show Target where
+  show (Target t _ _ _ _) = T.unpack t
+
+instance Eq Stage where
+  (==) (Stage n _ _ _ _) (Stage n2 _ _ _ _) = n Prelude.== n2
+
+instance Eq Target where
+  (==) (Target n _ _ _ _) (Target n2 _ _ _ _) = n Prelude.== n2
+
+instance Ord Target where
+  compare (Target n _ _ _ _) (Target n2 _ _ _ _) = compare n n2
+
+-- | Typeclass representing data types that can be used to collect files
+-- for input into a target.
+class GatherStrategy a where
+  -- | Function that, given the strategy, will produce a list of file paths.
+  gather :: a -> IO [T.Text]
+
+-- | Existential data type for wrapping 'GatherStrategy's so they can be used
+-- as a uniform type.
+data Gatherer = forall s . GatherStrategy s => Gatherer s
+
+-- | Type wrapper for a function to determine if a file should be returned by
+-- a 'Gatherer'.
+type FilterFunc = (T.Text -> Bool)
+
+-- | 'Gatherer' that will return exactly one file.
+data SingleFileGatherer = SingleFileGatherer T.Text
+
+-- | 'Gatherer' that will return all files in a given directory (but not its
+-- subdirectories) that pass the filter.
+data DirectoryGatherer = DirectoryGatherer T.Text FilterFunc
+
+-- | 'Gatherer' that will return all files in a given directory tree that pass
+-- the filter.
+data FileTreeGatherer = FileTreeGatherer T.Text FilterFunc
+
+-- | 'Gatherer' that will run a command and return an empty list.
+-- Useful for making clean 'Target's. Use sparingly.
+data CommandGatherer = CommandGatherer (IO ())
+
+-- Horrible.
+instance Serialize.Serialize T.Text where
+  put s = Serialize.putListOf Serialize.put $ T.unpack s
+  get = T.pack <$> Serialize.getListOf Serialize.get
diff --git a/src/Dib/Util.hs b/src/Dib/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/Dib/Util.hs
@@ -0,0 +1,18 @@
+-- Copyright (c) 2010-2016 Brett Lajzer
+-- See LICENSE for license information.
+
+-- | Module containing utility functions and common functionality.
+module Dib.Util (
+  getSubDirectories
+  ) where
+
+import Control.Monad
+import qualified System.Directory as D
+
+-- | Given a directory, returns the list of subdirectories.
+getSubDirectories :: FilePath -> IO [FilePath]
+getSubDirectories root = do
+  contents <- D.getDirectoryContents root
+  let filteredDirs = filter (\x -> x /= "." && x /= "..") contents
+  dirs <- filterM (\(x, _) -> D.doesDirectoryExist x) $ zip (map (root ++) filteredDirs) filteredDirs
+  return $ map snd dirs
