cartel-0.16.0.0: lib/Cartel.hs
-- | Cartel - a library to specify Cabal files in Haskell
--
-- The Cabal file format works very well for small projects.
-- However, in big projects with a library, many executables, and
-- test suites, some irritations emerge. You need to specify
-- dependencies in multiple places, leading to redundancy. You also
-- have to manually add in new modules, make sure you list all
-- modules (a real pain with executables, as problems may arise only
-- after you build a distribution tarball), and update your module
-- lists when you refactor.
--
-- Specifying your Cabal files in Haskell rather than in a
-- plain-text file format helps deal with a lot of these problems.
-- You have the full power of Haskell to make definitions in one
-- place and then reuse them. You can also dynamically read a tree
-- of modules and use the result, thus avoiding the need to manually
-- update your module lists.
--
-- A disadvantage to Cartel is that is more verbose than a vanilla
-- Cabal file. In addition, you also have to remember to generate the
-- new Cabal file whenever you change the script that generates your
-- Cabal file.
--
-- To the extent possible, Cartel uses the Haskell type system to
-- prevent you from making mistakes in your Cabal file. For example,
-- the 'Betsy' type prevents you from using 'flag's that you have not
-- declared, and you can't put an 'exposedModules' field in anything
-- but a library. However, Cartel does not prevent against all
-- errors. For example, Cartel does nothing to prevent you from
-- applying a function that calls for a 'NonEmptyString' to a string
-- that is, in fact, empty. Another example is that Cabal requires
-- executables to have a @main-is@ field, but Cartel does not force
-- you to include one. Ultimately your Cabal file might still have
-- errors that you have to fix by changing the program that generates
-- the file.
--
-- I highly recommend that you use Cartel with @stack@, a Haskell
-- build tool. Stack is available at
--
-- <https://github.com/commercialhaskell/stack>
--
-- By using @stack@ as a script interpreter, you can help ensure that
-- your Cartel script runs well into the future. This is described at
--
-- <http://docs.haskellstack.org/en/stable/GUIDE.html#ghc-runghc>
--
-- In particular, to have a Cartel script that uses @stack@, you do
-- something like this in your Cartel script:
--
-- @
-- #!\/usr/\bin\/env\/stack
-- -- stack --resolver lts-5.0 --install-ghc runghc --package cartel
-- module Main where
-- import qualified Cartel as C
-- -- etc...
-- @
--
-- However, if you have a @stack.yaml@ file in the same directory as
-- your Cabal file, @stack@ will get confused. To work around this,
-- temporarily rename your @stack.yaml@ file while you use your Cartel
-- script (you can easily write a script to automate this).
--
-- Everything you usually need is in this module. Other Cartel
-- modules contain implementation details. /See first/ the
-- 'NonEmptyString' type synonym, which has important details on how
-- to regard 'String's and 'NonEmptyString's as you read the
-- documentation. Also, examine "Cartel.GenCartelCabal", which
-- generates Cartel's own Cabal file using Cartel.
--
-- Hopefully this module's documentation is organized so that
-- top-to-bottom reading will make sense.
module Cartel
( -- * Basic types and classes
Word
, NonEmptyString
, Version
-- * Sections
, Section
-- * Repositories
-- ** Version control systems
, Vcs
, darcs, git, svn, mercurial, bazaar, archVcs, monotone, cvs
-- ** Repository kinds
, RepoKind
, repoHead, repoThis
-- ** Building repositories
, Repository(..)
, githubHead
, repository
-- * Logicals
, LogicTree(..)
, invert
-- ** Constraints
, Constraint
, lt
, gt
, eq
, ltEq
, gtEq
, anyVersion
-- ** Conditionals
, Compiler
, ghc, nhc, yhc, hugs, helium, jhc, lhc
, Condition
, system
, arch
, impl
, flag
, true
, false
, condBlock
-- * Packages
, Package
, package
-- ** Package helpers
, closedOpen
, apiVersion
, nextBreaking
, nextMajor
, exactly
, atLeast
, unconstrained
-- * Build information
-- | Libraries, executables, test suites, and benchmarks all share
-- common fields for build information. 'BuildInfoField' represents
-- these common fields, and 'HasBuildInfo' is a typeclass encompassing
-- libraries, executables, test suites, and benchmarks. You can
-- build these fields for any of these sections using the functions
-- and values listed here.
, BuildInfoField
, HasBuildInfo
, haskell98
, haskell2010
, buildDepends
, otherModules
, hsSourceDirs
, extensions
, defaultExtensions
, otherExtensions
, buildTools
, buildable
, ghcOptions
, ghcProfOptions
, ghcSharedOptions
, hugsOptions
, nhc98Options
, includes
, installIncludes
, includeDirs
, cSources
, extraLibraries
, extraLibDirs
, ccOptions
, cppOptions
, ldOptions
, pkgConfigDepends
, frameworks
-- * BuildsExe
--
-- | Benchmarks, test suites, and executables have common fields;
-- the 'BuildsExe' class captures these.
, BuildsExe(..)
-- * Exitcode executables
--
-- | Test suites and benchmarks are capable of building things of
-- type @exitcode-stdio-1.0@; the 'BuildsExitcode' class reflects
-- this.
, BuildsExitcode(..)
, exitcodeFields
-- * Betsy
, Betsy
-- ** Flags
, FlagName
, FlagOpts(..)
, Flag(..)
, makeFlag
, currentFlags
-- * Libraries
--
-- | A library consists of one or more 'LibraryField's. Typically
-- you will return these fields inside of the 'Betsy' type through
-- the 'defaultMain' function. To build a 'LibraryField', you will
-- mostly use the bindings in the \"Build Information\" section of
-- this module. You will also need 'exposedModules' and you might
-- use 'exposed' and 'condBlock'.
, LibraryField
, exposed
, exposedModules
-- * Executables
--
-- | An executable consists of one more more 'ExecutableField's.
-- You build an executable by passing one or more 'ExecutableField's
-- to the 'executable' function. To get an 'ExecutableField', you
-- will mostly use the bindings in the \"Build Information\" section
-- of this module, as well as 'mainIs'. You might also need
-- 'condBlock'.
, ExecutableField
, executable
-- * Test suites
--
-- | A test suite consists of one more more 'TestSuiteField's. You
-- build an test suite by passing one or more 'TestSuiteField's to
-- the 'testSuite' function. To get a 'TestSuiteField', you will
-- mostly use the bindings in the \"Build Information\" section of
-- this module. You might also need the 'testModule',
-- 'exitcodeStdio', 'mainIs', 'detailed', 'condBlock', and
-- 'exitcodeFields' bindings.
, detailed
, TestSuiteField
, testModule
, testSuite
-- * Benchmarks
--
-- | A benchmark consists of one more more 'BenchmarkField's. You
-- build an benchmark by passing one or more 'BenchmarkField's to
-- the 'benchmark' function. To get an 'BenchmarkField', you will
-- mostly use the bindings in the \"Build Information\" section of
-- this module. You might also need the 'exitcodeStdio',
-- 'exitcodeFields', and 'condBlock' bindings.
, BenchmarkField
, benchmark
-- * Getting module lists
, fileExtensions
, modulesWithExtensions
, modules
-- * Properties
-- ** Build types
, BuildType
, simple, configure, make, custom
-- ** Licenses
, License
, gpl, agpl, lgpl, bsd2, bsd3, bsd4, mit, mpl, apache, publicDomain
, allRightsReserved, otherLicense
, Properties(..)
-- * Generating Cabal files
, defaultMain
, defaultMainWithHeader
) where
-- # begin imports
import Cartel.Ast
import Cartel.Betsy
import Cartel.Render
import Cartel.Types
import qualified Cartel.Version
import Data.List (intersperse)
import Data.Time
import System.Environment (getProgName)
import System.Exit (exitFailure, exitSuccess)
import qualified System.IO as IO
-- # end imports
-- | Renders a header for the Cabal file.
header
:: String
-- ^ Name of program used
-> ZonedTime
-- ^ When this output is being created
-> String
header nm zt = hdr
where
hdr = unlines $
[ "-- This Cabal file generated using the Cartel library."
, "-- Cartel is available at:"
, "-- http://www.github.com/massysett/cartel"
, "--"
] ++ case nm of
[] -> []
_ -> ["-- Script name used to generate: " ++ nm]
++
[ "-- Generated on: " ++ show zt
, "-- Cartel library version: " ++ showVer
]
showVer = concat
. intersperse "."
. map show
$ Cartel.Version.version
-- | Generic header to prepend to Cabal files.
genericHeader :: IO String
genericHeader = header <$> getProgName <*> getZonedTime
-- | Like 'defaultMain' but allows you to specify what header to
-- prepend to the output (if any).
defaultMainWithHeader
:: (Cabal -> IO String)
-- ^ Function that generates a header. This header will be
-- prepended to the resulting Cabal file; for instance, you might
-- place comments in this header. This function is applied to the
-- resulting Cabal record. For no header, just use
-- @const (return \"\")@ here.
--
-- The 'Cabal' type is not exported from this module to keep the
-- number of exported bindings down; you can @import@ it from
-- @Cartel.Ast@.
-> Betsy IO (Properties, [LibraryField], [Section])
-- ^ Computation that creates the package information. 'Betsy'
-- creates 'Flag's. The 'Betsy' type constructor is appled to 'IO'
-- so that functions such as 'modules' can do IO to query the file
-- system.
-> IO ()
-- ^ Prints Cabal file to standard output if there were no errors
-- along the way; otherwise, prints a message to standard error and
-- exits unsuccessfully.
defaultMainWithHeader mkHdr btsy = do
ei <- runBetsy btsy
((prop, libs, secs), flgs) <- case ei of
Left e -> do
IO.hPutStr IO.stderr . renderNoIndent $ e
exitFailure
Right g -> return g
let cbl = Cabal prop libs secs flgs
hdr <- mkHdr cbl
IO.hSetBinaryMode IO.stdout False
IO.hSetEncoding IO.stdout IO.utf8
putStr . vsep $ [hdr, renderNoIndent cbl]
exitSuccess
-- | Generates a Cabal file. If you have no library, just leave the
-- list of 'LibraryField' empty. Include any and all executables,
-- test suites, benchmarks, and repositories in the list of 'Section'.
-- Ensures that the generated Cabal file also includes any flags you
-- made with 'makeFlag'. If there is an error (such as a duplicate
-- flag) an error message is printed to standard error and the program
-- invokes 'exitFailure'; otherwise, the generated Cabal file is
-- printed to standard output and the program invokes 'exitSuccess'.
-- Output will always be UTF-8, consistent with Cabal's requirements.
--
-- Includes a header showing that the Cabal file was auto-generated
-- and the program name that generated the Cabal file, along with when
-- it was generated. This gives a clue to readers who see a Cabal
-- file in the distributed tarball but who would get confused when there
-- isn't one in the version controlled sources. To omit the header,
-- use 'defaultMainWithHeader'.
defaultMain
:: Betsy IO (Properties, [LibraryField], [Section])
-- ^ Computation that creates the package information. 'Betsy'
-- creates 'Flag's. The 'Betsy' type constructor is appled to 'IO'
-- so that functions such as 'modules' can do IO to query the file
-- system.
-> IO ()
-- ^ Prints Cabal file to standard output if there were no errors
-- along the way; otherwise, prints a message to standard error and
-- exits unsuccessfully.
defaultMain = defaultMainWithHeader (const genericHeader)