diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2014, Omari Norman
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Omari Norman nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,98 @@
+# sunlight
+
+sunlight helps you test your Cabalized package against multiple
+dependency versions.
+
+## Why?
+
+I wrote sunlight because of minor quibbles I have with the Haskell
+[Package Versioning
+Policy](http://www.haskell.org/haskellwiki/Package_versioning_policy).
+The PVP encourages you to record top bounds on each of the
+dependencies in your package.  This often keeps packages from
+building when dependencies are updated, even if the dependencies
+have changed in harmless ways.
+
+I'm not the only one with quibbles here: [Haskell Cafe
+discussion](http://www.haskell.org/pipermail/haskell-cafe/2012-August/102885.html).
+My only quibble is with upper bounds; specifying the release of a
+package by using the PVP is an excellent practice.
+
+I figure it would be much more useful for the Cabal file to serve as
+documentation of software versions that the package will build with.
+The problem is that there was no easy way to make sure that a
+package builds with the minimum versions specified in the Cabal
+file.  For example, cabal install does not have an option to fetch
+the lowest version of each dependency; instead, it eagerly fetches
+the most recent dependencies--which is what you want most of the
+time, but this makes it hard to ensure that the package builds with
+the lowest possible dependencies.
+
+sunlight automatically builds the package using multiple GHC
+versions, so you can verify that the package builds even with
+different dependencies or compiler versions.  This also makes it
+easier to maintain a package that builds with older GHC versions
+that you may not use regularly.
+
+## Usage
+
+To use sunlight, you need to specify a minimum bound for each
+dependency.  That minimum bound must be resolvable to an actual
+version of the package.  It's OK to specify an exact bound, or an
+optional maximum bound, but there must be a minimum.
+
+For example, these are all acceptable:
+
+    bytestring ##0.9.2.1
+    bytestring ># 0.9.2.1 && < 0.10
+    bytestring ># 0.9.2.1
+
+This is not acceptable because there is no bound at all:
+
+    bytestring
+
+This is not acceptable because there is no lower bound:
+
+    bytestring <# 0.10
+
+Then you build a test executable.  An example is in the
+`sunlight-test.hs` file in the sunlight git tree.  This program
+tests sunlight itself.
+
+The executable will output a number of reports in a `sunlight`
+directory, and it will also output a `current-versions.txt` report
+to show you the most recent versions of dependencies that work with
+your package, as well as a `minimum-versions.txt` file showing you
+that your package builds and tests with the minimum package
+versions.
+
+Also, see the Haddocks on the Test.Sunlight module.
+
+## Disclaimer
+
+sunlight works, but I'm not sure it is a good solution to the
+problem of managing bounds and dependencies in a Cabal file.  In
+particular, it might be a better long-term idea to change
+cabal-install so that it can fetch the lowest versions rather than
+the most recent ones.
+
+sunlight works on simple Cabal files but I have not tested it on any
+exotic files.
+
+## See also
+
+cabal-bounds - program to automatically manage the bounds and
+versions in a Cabal file.
+[Github](https://github.com/dan-t/cabal-bounds)
+[Hackage](http://hackage.haskell.org/package/cabal-bounds)
+
+## License
+
+sunlight is licensed under the BSD license.
+
+## Why the name?
+
+The word "cabal" has shadowy connotations.  This package helps
+ensure your cabal is doing what you say it is doing...so it helps
+shine sunlight on your cabal which is, as they say, the best
+disinfectant.
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/lib/Test/Sunlight.hs b/lib/Test/Sunlight.hs
new file mode 100644
--- /dev/null
+++ b/lib/Test/Sunlight.hs
@@ -0,0 +1,564 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Test.Sunlight where
+
+import Distribution.Package
+import Distribution.Text
+import Test.Sunlight.Shell
+import System.Directory
+import Distribution.PackageDescription
+import Distribution.PackageDescription.Parse
+import Distribution.Simple.Utils
+import Distribution.Verbosity
+import Distribution.Version
+import Data.Tuple.Select
+import qualified Data.ByteString.Char8 as BS8
+import Data.Monoid
+import Data.Time
+import System.Locale (defaultTimeLocale)
+import Data.List (intersperse)
+import System.Random
+import Control.Monad
+import System.Exit
+
+
+-- | Result from installing the package's dependencies.
+data InstallResult = InstallResult
+  { drOutput :: CmdResult
+  , drGhcPkg :: CmdResult
+  } deriving Show
+
+instance CheckOk InstallResult where
+  isOk r = all isOk . map ($ r) $ [drOutput, drGhcPkg]
+
+-- | Install a package's dependencies.
+
+-- Preconditions:
+--
+-- * current directory has the unpacked tarball.
+--
+-- Side effects:
+--
+-- * dependencies are fully or partially installed.  If partially
+-- installed, the ExitCode should be non-zero.
+installDeps
+  :: [PackageIdentifier]
+  -- ^ Optional constraints.  Currently constraints may only be tied
+  -- to specific versions (for instance, flag constraints or
+  -- constraints tied to a range of versions are not allowed.)
+  -> FilePath
+  -- ^ Install using this compiler (full path to compiler).
+  -> FilePath
+  -- ^ Full path to ghc-pkg.
+  -> FilePath
+  -- ^ Path to cabal executable
+  -> FilePath
+  -- ^ Path to directory to use for user package DB.
+  -> FilePath
+  -- ^ Directory to use for installation prefix
+  -> IO InstallResult
+installDeps cs ghc pkg cabal db dir = do
+  let opts =
+        [ "install"
+        , "--verbose=2"
+        , "--with-compiler=" ++ ghc
+        , "--with-hc-pkg=" ++ pkg
+        , "--prefix=" ++ dir
+        , "--disable-library-profiling"
+        , "--disable-executable-profiling"
+        , "--package-db=clear"
+        , "--package-db=global"
+        , "--package-db=" ++ db
+        , "--enable-tests"
+        , "--disable-documentation"
+        , "--only-dependencies"
+        ] ++ map constraint cs
+  out <- tee cabal opts
+  let pkgOpts = [ "list", "--global", "--package-conf=" ++ db ]
+  pkgOut <- tee pkg pkgOpts
+  return $ InstallResult out pkgOut
+
+-- | Makes a PackageIdentifier into a version constraint option.
+constraint :: PackageIdentifier -> String
+constraint i = "--constraint=" ++ name ++ "==" ++ ver
+  where
+    name = display . pkgName $ i
+    ver = display . pkgVersion $ i
+
+data PackageInstResult = PackageInstResult
+  { piSetup :: CmdResult
+  , piConfigure :: CmdResult
+  , piBuild :: CmdResult
+  , piInst :: CmdResult
+  , piGhcPkg :: CmdResult
+  } deriving Show
+
+instance CheckOk PackageInstResult where
+  isOk r = all isOk . map ($ r) $
+    [ piSetup, piConfigure, piBuild, piInst, piGhcPkg ]
+
+-- | Install a package.
+--
+-- Preconditions:
+--
+-- * dependencies have already been installed at the given prefix
+-- and registered in the given db
+--
+-- * current directory has the unpacked tarball.
+--
+-- Side effects:
+--
+-- * package should be installed, or exit code is non-zero
+installPackage
+  :: FilePath
+  -- ^ Install using this compiler (full path to compiler)
+  -> FilePath
+  -- ^ Full path to ghc-pkg
+  -> FilePath
+  -- ^ User DB
+  -> FilePath
+  -- ^ Installation prefix
+  -> IO PackageInstResult
+installPackage ghc pkg db pfx = do
+  let opts =
+        [ "configure"
+        , "--verbose=2"
+        , "--with-compiler=" ++ ghc
+        , "--with-hc-pkg=" ++ pkg
+        , "--prefix=" ++ pfx
+        , "--package-db=clear"
+        , "--package-db=global"
+        , "--package-db=" ++ db
+        , "--enable-tests"
+        ]
+  rBuildSetup <- tee "ghc" ["--make", "Setup.hs"]
+  rConf <- tee "./Setup" opts
+  let bOpts =
+        [ "build", "--verbose=2" ]
+  rBuild <- tee "./Setup" bOpts
+  rInst <- tee "./Setup" ["install", "--verbose=2"]
+  rPkg <- tee pkg
+    [ "list"
+    , "--global"
+    , "--package-conf=" ++ db
+    ]
+  return $ PackageInstResult rBuildSetup rConf rBuild rInst rPkg
+
+-- | Test a package.
+--
+-- Preconditions:
+--
+-- * Package has already been built.
+testPackage
+  :: [(String, [String])]
+  -- ^ From the package root directory, these are the commands and
+  -- arguments to run to test the package.
+  -> IO [CmdResult]
+testPackage = mapM (uncurry tee)
+
+data InstallAndTestResult = InstallAndTestResult
+  { itDate :: UTCTime
+  -- ^ Current time
+  , itGhcVersion :: CmdResult
+  -- ^ Result of ghc --version
+  , itPkgVersion :: CmdResult
+  -- ^ Result of ghc-pkg --version
+  , itCabalVersion :: CmdResult
+  -- ^ Result of cabal --version
+  , itCompileSetup :: CmdResult
+  -- ^ Result from compiling Setup.hs
+  , itSdistDeps :: CmdResult
+  -- ^ Result from running sdist to create tree from which to
+  -- install dependencies
+  , itSdistPkg :: CmdResult
+  -- ^ Result from running sdist to create tree from which to
+  -- install package
+  , itInit :: CmdResult
+  -- ^ Result from initializing user package DB
+  , itDeps :: InstallResult
+  , itPackage :: PackageInstResult
+  , itTest :: [CmdResult]
+  } deriving Show
+
+instance CheckOk InstallAndTestResult where
+  isOk r = and [ rsltsOk, testsOk,
+    isOk (itDeps r), isOk (itPackage r) ]
+    where
+      rsltsOk = all isOk . map ($ r) $
+        [ itGhcVersion, itPkgVersion, itCabalVersion, itCompileSetup,
+          itSdistDeps, itSdistPkg, itInit ]
+      testsOk = all isOk . itTest $ r
+
+-- | Performs test installation.
+--
+-- Preconditions:
+--
+-- * is run from root package directory.  A temporary directory is
+-- created inside this directory.  All work is done within the
+-- temporary directory.
+--
+-- Postconditions:
+--
+-- * cleans up after itself; no temporary files should remain.
+installAndTest
+  :: [PackageIdentifier]
+  -- ^ Constraints
+  -> UTCTime
+  -> FilePath
+  -- ^ Path to compiler
+  -> FilePath
+  -- ^ Path to ghc-pkg
+  -> FilePath
+  -- ^ Path to cabal executable
+  -> [(String, [String])]
+  -- ^ How to test the package
+  -> IO InstallAndTestResult
+installAndTest cs date ghc pkg cabal test =
+  withTempDirectory verbose "." "sunlight" $ \relDir -> do
+    dir <- canonicalizePath relDir
+    let setup = dir ++ "/Setup"
+        distDeps = dir ++ "/distDeps"
+        distPkg = dir ++ "/distPkg"
+        db = dir ++ "/db"
+        pfx = dir ++ "/prefix"
+    ghcVer <- tee ghc ["--version"]
+    pkgVer <- tee pkg ["--version"]
+    cblVer <- tee "cabal" ["--version"]
+    rSetup <- tee ghc ["--make", "-outputdir", dir,
+      "-o", setup, "Setup.hs"]
+    rDistDeps <- tee setup
+      ["sdist", "--output-directory=" ++ distDeps ]
+    rDistPkg <- tee setup
+      ["sdist", "--output-directory=" ++ distPkg ]
+    createDirectory pfx
+    rInit <- tee pkg ["init", db]
+    rDeps <- inDirectory distDeps $
+      installDeps cs ghc pkg cabal db pfx
+    rInst <- inDirectory distPkg
+      $ installPackage ghc pkg db pfx
+    rTest <- inDirectory distPkg $ testPackage test
+    return $ InstallAndTestResult date ghcVer pkgVer cblVer
+      rSetup rDistDeps rDistPkg
+      rInit rDeps rInst rTest
+
+-- | Gets a list of PackageIdentifier with the lowest possible
+-- versions.  Fails if a package has a dependency range with no
+-- minimum.
+lowestVersions
+  :: GenericPackageDescription
+  -> Either Dependency [PackageIdentifier]
+  -- ^ Left with the bad dependency if there is one; Right
+  -- otherwise.
+lowestVersions pd = mapM lowestVersion ls
+  where
+    ls = depsLib ++ depsExe ++ depsTest ++ depsBench
+    depsLib = case condLibrary pd of
+      Nothing -> []
+      Just deps -> getDependencies deps
+    getDeps = getDepsList pd
+    depsExe = getDeps condExecutables
+    depsTest = getDeps condTestSuites
+    depsBench = getDeps condBenchmarks
+
+getDepsList
+  :: GenericPackageDescription
+  -> (GenericPackageDescription -> [(a, CondTree b [Dependency] c)])
+  -> [Dependency]
+getDepsList d f = concatMap getDependencies . map snd . f $ d
+
+getDependencies
+  :: CondTree v [Dependency] a
+  -> [Dependency]
+getDependencies t =
+  let this = condTreeConstraints t
+      rest = concatMap getDependencies . map sel2
+        . condTreeComponents $ t
+  in this ++ rest
+
+lowestVersion :: Dependency -> Either Dependency PackageIdentifier
+lowestVersion d@(Dependency n r) = case asVersionIntervals r of
+  [] -> Left d
+  (LowerBound v b, _):_
+    | b == ExclusiveBound -> Left d
+    | otherwise -> Right $ PackageIdentifier n v
+
+testLowestVersions
+  :: UTCTime
+  -> FilePath
+  -- ^ Path to compiler
+  -> FilePath
+  -- ^ Path to ghc-pkg
+  -> FilePath
+  -- ^ Path to cabal executable
+  -> [(String, [String])]
+  -- ^ How to test the package
+  -> GenericPackageDescription
+  -> Either Dependency (IO InstallAndTestResult)
+testLowestVersions date ghc pkg cabal test d = case lowestVersions d of
+  Left e -> Left e
+  Right ps -> Right $ installAndTest ps date ghc pkg cabal test
+
+testDefaultVersions
+  :: UTCTime
+  -> FilePath
+  -- ^ Path to compiler
+  -> FilePath
+  -- ^ Path to ghc-pkg
+  -> FilePath
+  -- ^ Path to cabal executable
+  -> [(String, [String])]
+  -- ^ How to test the package
+  -> IO InstallAndTestResult
+testDefaultVersions date ghc pkg cabal test =
+  installAndTest [] date ghc pkg cabal test
+
+
+testMultipleVersions
+  :: UTCTime
+  -> (FilePath, FilePath)
+  -- ^ Compiler and ghc-pkg to use when testing lowest version
+  -> [(FilePath, FilePath)]
+  -- ^ Compilers and ghc-pkg to use to test default versions
+  -> FilePath
+  -- ^ Path to cabal executable
+  -> [(String, [String])]
+  -- ^ How to test package
+  -> GenericPackageDescription
+  -> Either Dependency
+            (IO (InstallAndTestResult, [InstallAndTestResult]))
+testMultipleVersions date (lowestGhc, lowestPkg) rest cabal test pd =
+  case testLowestVersions date lowestGhc lowestPkg cabal test pd of
+    Left d -> Left d
+    Right g -> Right $ do
+      r1 <- g
+      let testRest (c, p) = testDefaultVersions date c p cabal test
+      rs <- mapM testRest rest
+      return (r1, rs)
+
+-- Sample test directory structure:
+-- minimum-versions.txt
+-- current-versions.txt
+-- sunlight
+--  - 2014
+--    - 02
+--      - 22
+--        - UTCtime
+--          - [random string]
+--            - lowest-7.4.2-passed.txt
+--            - current-7.4.2-passed.txt
+--            - current-7.6.1-passed.txt
+
+versionsReport
+  :: BS8.ByteString
+  -- ^ Description of dependencies
+  -> Compiler
+  -> Description
+  -> UTCTime
+  -> InstallAndTestResult
+  -> BS8.ByteString
+versionsReport desc c d t r = BS8.concat
+  [ "This package was tested to work with these dependency\n"
+  , "versions and compiler version.\n"
+  , desc
+  , "Tested as of: " <> (BS8.pack . show $ t) <> "\n"
+  , "Path to compiler: " <> BS8.pack c <> "\n"
+  , "Compiler description: " <> BS8.pack d <> "\n"
+  , "\n"
+  ] <>
+  (crStdOut . piGhcPkg . itPackage $ r)
+
+minimumVersionsReport
+  :: Compiler
+  -> Description
+  -> UTCTime
+  -> InstallAndTestResult
+  -> BS8.ByteString
+minimumVersionsReport = versionsReport
+  "These are the minimum versions given in the .cabal file.\n"
+
+writeMinimumVersionsReport :: BS8.ByteString -> IO ()
+writeMinimumVersionsReport = BS8.writeFile "minimum-versions.txt"
+
+currentVersionsReport
+  :: UTCTime
+  -> [(Description, Compiler, a)]
+  -> [InstallAndTestResult]
+  -> Maybe BS8.ByteString
+currentVersionsReport t ds ts
+  | null ds || null ts = Nothing
+  | otherwise = Just $ versionsReport dep comp desc t r
+  where
+    dep = "These are the default versions fetched by cabal install.\n"
+    (desc, comp, _) = last ds
+    r = last ts
+
+writeCurrentVersionsReport :: BS8.ByteString -> IO ()
+writeCurrentVersionsReport = BS8.writeFile "current-versions.txt"
+
+
+instance ShowBS InstallResult where
+  showBS r = showBS (drOutput r)
+    <> showBS (drGhcPkg r)
+
+instance ShowBS PackageInstResult where
+  showBS r =
+    showBS (piSetup r)
+    <> showBS (piConfigure r)
+    <> showBS (piBuild r)
+    <> showBS (piInst r)
+    <> showBS (piGhcPkg r)
+
+instance ShowBS InstallAndTestResult where
+  showBS i =
+    "Current time: " <> showBS (itDate i) <> "\n"
+    <> showBS (itGhcVersion i)
+    <> showBS (itPkgVersion i)
+    <> showBS (itCabalVersion i)
+    <> showBS (itCompileSetup i)
+    <> showBS (itSdistDeps i)
+    <> showBS (itSdistPkg i)
+    <> showBS (itInit i)
+    <> showBS (itDeps i)
+    <> showBS (itPackage i)
+    <> (BS8.concat . map showBS . itTest $ i)
+
+-- | Gets four-character random string.
+randomString :: IO String
+randomString =
+  fmap (map toEnum)
+  $ replicateM 4 (getStdRandom (randomR (fromEnum 'a', fromEnum 'z')))
+
+makeResultFile
+  :: UTCTime
+  -> String
+  -- ^ Random string
+  -> String
+  -- ^ Description string, e.g. "lowest" or "current"
+  -> String
+  -- ^ Compiler version description
+  -> InstallAndTestResult
+  -> IO ()
+makeResultFile utct rand desc comp res = do
+  let dir = resultDirectory utct rand
+  createDirectoryIfMissing True dir
+  let fn = dir ++ "/" ++ desc ++ "-" ++ comp ++ "-"
+        ++ passFail ++ ".txt"
+      passFail
+        | isOk res = "passed"
+        | otherwise = "FAILED"
+  BS8.writeFile fn . showBS $ res
+
+
+resultDirectory
+  :: UTCTime
+  -> String
+  -- ^ Random string
+  -> String
+resultDirectory t s = concat . intersperse "/" $
+  ["sunlight", yr, mo, dy, ti, s ]
+  where
+    (y, m, d) = toGregorian . utctDay $ t
+    yr = show y
+    mo = pad . show $ m
+    dy = pad . show $ d
+    pad str
+      | length str > 1 = str
+      | otherwise = '0':str
+    ti = formatTime defaultTimeLocale "%H%M%S" t
+
+-- | A description for this compiler version.  This will be used in
+-- the directory name in the tree that is written to disk.  I simply
+-- use a compiler version, such as @7.4@.
+type Description = String
+
+-- | Path to GHC compiler.  You can use a full path name or, if you use
+-- just an executable name, the PATH will be searched.
+type Compiler = String
+
+-- | Path to ghc-pkg.  If you use just an executable name, the PATH
+-- will be searched.
+type GhcPkg = String
+
+-- | Path to cabal executable.  Used to install package
+-- dependencies.  If you use just an executable name, the PATH will
+-- be searched.
+type Cabal = String
+
+data TestInputs = TestInputs
+  { tiDescription :: Maybe GenericPackageDescription
+  -- ^ If Just, use this package description.  Handy if you write
+  -- your own package description in Haskell rather than in the
+  -- cabal format.  Otherwise, use Nothing and sunlight will look
+  -- for the default cabal file and parse that.
+
+  , tiCabal :: Cabal
+  -- ^ Which @cabal@ executable to use.
+
+  , tiLowest :: (Description, Compiler, GhcPkg)
+  -- ^ Test the minimum dependency bounds using this compiler.  A
+  -- report is left in the main package directory showing which
+  -- package versions worked with this compiler and with the minimum
+  -- bounds.
+
+  , tiDefault :: [(Description, Compiler, GhcPkg)]
+  -- ^ Test the default dependencies using these compilers.  Since
+  -- cabal-install will eagerly get the most recent dependencies it
+  -- can find, this will test the highest possible versions.  The
+  -- compiler specified in 'tiLowest' is not automatically retried
+  -- here, so if you want to use that compiler specify it as well.
+  --
+  -- The last compiler in this list is assumed to be the most recent
+  -- compiler.  A report is left in the main package directory
+  -- showing the dependencies that worked with this compiler
+  -- version.
+
+  , tiTest :: [(String, [String])]
+  --  ^ How to test the package.  Each pair is a command to run,
+  --  and the arguments to that command.  The command is run from
+  --  the resulting package root directory after the package is
+  --  unpacked and compiled.  So, if your test suite is an
+  --  executable called @myTest@ and it requires the argument
+  --  @--verbose@, you would use something like this:
+  --
+  -- > [("dist/build/myTest/myTest", ["--verbose"])]
+  --
+  -- The tests are considered to pass if all these commands exit
+  -- with a zero exit status.
+  --
+  -- You can also abuse this option to get output in the record of
+  -- the test; for instance, maybe you want to record the current
+  -- git HEAD:
+  --
+  -- > [("git", ["rev-parse", "HEAD"])]
+  } deriving Show
+
+runTests :: TestInputs -> IO ()
+runTests i = do
+  desc <- case tiDescription i of
+    Just pd -> return pd
+    Nothing -> do
+      path <- defaultPackageDesc verbose
+      readPackageDescription verbose path
+  date <- getCurrentTime
+  randStr <- randomString
+  let last2 (_, b, c) = (b, c)
+      eiRes = testMultipleVersions date
+        (last2 . tiLowest $ i) (map last2 . tiDefault $ i)
+        (tiCabal i) (tiTest i) desc
+  (r1, rs) <- case eiRes of
+    Left e -> dependencyError e
+    Right g -> g
+  makeResultFile date randStr "lowest" (sel1 . tiLowest $ i) r1
+  let makeRest r (d, _, _) = makeResultFile date randStr "current" d r
+  _ <- zipWithM makeRest rs (tiDefault i)
+  when (not $ isOk r1 && all isOk rs) exitFailure
+  writeMinimumVersionsReport $ minimumVersionsReport
+    (sel2 . tiLowest $ i) (sel1 . tiLowest $ i) date r1
+  maybe (return ()) writeCurrentVersionsReport $ currentVersionsReport
+    date (tiDefault i) rs
+  exitSuccess
+
+dependencyError :: Dependency -> IO a
+dependencyError d = putStrLn s >> exitFailure
+  where
+    s = "dependency invalid: " ++ show d ++ " sunlight requires "
+      ++ "that you specify a fixed lower bound for each dependency."
diff --git a/lib/Test/Sunlight/Shell.hs b/lib/Test/Sunlight/Shell.hs
new file mode 100644
--- /dev/null
+++ b/lib/Test/Sunlight/Shell.hs
@@ -0,0 +1,151 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Test.Sunlight.Shell where
+
+
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Char8 as BS8
+import Control.Exception
+import Data.Monoid
+import Foreign.Safe
+import System.Directory
+import System.Exit
+import System.IO
+import System.Process
+import Control.Concurrent
+import Data.Time
+
+-- | Runs an IO action within the given directory, and then returns
+-- to the original directory.
+inDirectory
+  :: FilePath
+  -> IO a
+  -> IO a
+inDirectory inside a = bracket get rel run
+  where
+    get = do
+      d <- getCurrentDirectory
+      putStrLn $ "current directory is: " ++ d
+      putStrLn $ "changing to directory: " ++ inside
+      setCurrentDirectory inside
+      return d
+    rel d = do
+      putStrLn $ "returning to directory: " ++ d
+      setCurrentDirectory d
+    run = const a
+
+-- | Creates a process.  Its stdin is closed.  Returns handles to
+-- stdout and stderr, and the ProcessHandle.
+makeProcess
+  :: FilePath
+  -- ^ Run this command
+  -> [String]
+  -- ^ Arguments
+  -> IO (Handle, Handle, ProcessHandle)
+makeProcess c as = do
+  d <- getCurrentDirectory
+  putStrLn $ "current directory is: " ++ d
+  putStrLn $ "running: " ++ c ++ " " ++ show as
+  (Just hin, Just out, Just err, h) <- createProcess cp
+  hClose hin
+  return (out, err, h)
+  where
+    cp = CreateProcess { cmdspec = RawCommand c as
+                       , cwd = Nothing
+                       , env = Nothing
+                       , std_in = CreatePipe
+                       , std_out = CreatePipe
+                       , std_err = CreatePipe
+                       , close_fds = True
+                       , create_group = False }
+
+bufsize :: Int
+bufsize = 512
+
+data CmdResult = CmdResult
+  { crCode :: ExitCode
+  , crStdOut :: BS.ByteString
+  , crStdErr :: BS.ByteString
+  , crCommand :: String
+  , crArgs :: [String]
+  } deriving Show
+
+class ShowBS a where
+  showBS :: a -> BS.ByteString
+
+instance ShowBS CmdResult where
+  showBS r =
+    BS8.pack (crCommand r) <> " " <> (BS8.pack . show . crArgs $ r)
+    <> " "
+    <> case crCode r of
+        ExitSuccess -> "succeeded with exit code 0"
+        ExitFailure e -> "FAILED with exit code " <> BS8.pack (show e)
+    <> "\n"
+    <> "Standard output:\n"
+    <> shower (crStdOut r)
+    <> "\nStandard error:\n"
+    <> shower (crStdErr r)
+    <> "\n"
+    where
+      shower bs
+        | BS8.null bs = "<empty>\n"
+        | otherwise = bs
+
+instance ShowBS UTCTime where
+  showBS = BS8.pack . show
+
+
+resultOk :: CmdResult -> Bool
+resultOk c = case crCode c of
+  ExitSuccess -> True
+  _ -> False
+
+class CheckOk a where
+  isOk :: a -> Bool
+
+instance CheckOk CmdResult where
+  isOk c = case crCode c of
+    ExitSuccess -> True
+    _ -> False
+
+-- | Runs a command, sending standard output and standard error to
+-- the terminal, while also returning the standard output.
+tee
+  :: FilePath
+  -- ^ Run this command
+  -> [String]
+  -- ^ Arguments
+  -> IO CmdResult
+tee cmd as = bracket (makeProcess cmd as) rel go
+  where
+    rel (out, err, _) = hClose out >> hClose err
+
+    go (out, err, hdl) = do
+      mvOut <- newEmptyMVar
+      mvErr <- newEmptyMVar
+      _ <- forkIO (readToEOF out stdout >>= putMVar mvOut)
+      _ <- forkIO (readToEOF err stderr >>= putMVar mvErr)
+      bsOut <- takeMVar mvOut
+      bsErr <- takeMVar mvErr
+      cd <- waitForProcess hdl
+      return $ CmdResult cd bsOut bsErr cmd as
+
+
+-- | Reads from the given handle until EOF.  Sends results to the
+-- given handle and also stores them in the returned ByteString.
+readToEOF
+  :: Handle
+  -- ^ Read from this handle
+  -> Handle
+  -- ^ Send results to this handle
+  -> IO BS.ByteString
+readToEOF inp out = allocaBytes bufsize $ \ptr ->
+  let go bs = do
+        n <- hGetBufSome inp ptr bufsize
+        case n of
+          0 -> return bs
+          _ -> do
+            hPutBuf out ptr n
+            bs' <- BS.packCStringLen (ptr, n)
+            go (bs `BS.append` bs')
+  in go BS.empty
+
diff --git a/sunlight-test.hs b/sunlight-test.hs
new file mode 100644
--- /dev/null
+++ b/sunlight-test.hs
@@ -0,0 +1,19 @@
+-- | This file is an example of how you can use sunlight.  I
+-- actually use it to test sunlight itself.
+--
+-- I keep multiple versions of GHC installed.  Each one can be found
+-- in the PATH so there is no need to give exact paths to each one.
+module Main where
+
+import Test.Sunlight
+
+inputs = TestInputs
+  { tiDescription = Nothing
+  , tiCabal = "cabal"
+  , tiLowest = ("7.4", "ghc-7.4", "ghc-pkg-7.4")
+  , tiDefault = [ ("7.4", "ghc-7.4", "ghc-pkg-7.4")
+                , ("7.6", "ghc-7.6", "ghc-pkg-7.6") ]
+  , tiTest = []
+  }
+
+main = runTests inputs
diff --git a/sunlight.cabal b/sunlight.cabal
new file mode 100644
--- /dev/null
+++ b/sunlight.cabal
@@ -0,0 +1,53 @@
+-- Initial sunlight.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                sunlight
+version:             0.2.0.0
+synopsis:            Test Cabalized package against multiple dependency versions
+
+description:         
+  sunlight checks your Cabalized package and ensures that it actually
+  builds and passes its tests, even against mulitple dependency
+  versions specified in your Cabal file.  For example, your Cabal file
+  might say that your package builds against base-4.5.1.0
+  and base-4.6.0.1.  With
+  sunlight you can automatically verify that this is actually the case.
+  .
+  For more information, please see
+  .
+  <http://www.github.com/massysett/sunlight>
+
+homepage:            http://www.github.com/massysett/sunlight
+license:             BSD3
+license-file:        LICENSE
+author:              Omari Norman
+maintainer:          omari@smileystation.com
+stability:           experimental
+category:            Testing
+build-type:          Simple
+cabal-version:       >=1.10
+extra-source-files: sunlight-test.hs, README.md
+source-repository head
+  type: git
+  location: https://github.com/massysett/sunlight.git
+  
+
+library
+  exposed-modules:
+      Test.Sunlight
+    , Test.Sunlight.Shell
+
+  build-depends:
+      base >=4.5.1.0 && < 5
+    , directory >= 1.1.0.2
+    , bytestring >= 0.9.2.1
+    , Cabal >= 1.14.0
+    , process >= 1.1.0.1
+    , tuple >= 0.2.0.1
+    , time >= 1.4
+    , old-locale >= 1.0.0.4
+    , random >= 1.0.0.0
+
+  hs-source-dirs:      lib
+  ghc-options: -Wall
+  default-language:    Haskell2010
