diff --git a/hpack.cabal b/hpack.cabal
--- a/hpack.cabal
+++ b/hpack.cabal
@@ -3,7 +3,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           hpack
-version:        0.4.0
+version:        0.5.0
 synopsis:       An alternative format for Haskell packages
 homepage:       https://github.com/sol/hpack#readme
 bug-reports:    https://github.com/sol/hpack/issues
@@ -31,6 +31,7 @@
     , deepseq
     , directory
     , filepath
+    , Glob
     , text
     , unordered-containers
     , yaml
@@ -47,6 +48,7 @@
     , deepseq
     , directory
     , filepath
+    , Glob
     , text
     , unordered-containers
     , yaml
@@ -74,6 +76,7 @@
     , deepseq
     , directory
     , filepath
+    , Glob
     , text
     , unordered-containers
     , yaml
diff --git a/src/Hpack/Config.hs b/src/Hpack/Config.hs
--- a/src/Hpack/Config.hs
+++ b/src/Hpack/Config.hs
@@ -1,9 +1,14 @@
-{-# LANGUAGE DeriveGeneric #-}
-{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE DefaultSignatures #-}
 {-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveFoldable #-}
+{-# LANGUAGE DeriveTraversable #-}
 {-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE ViewPatterns #-}
 module Hpack.Config (
   packageConfig
 , readPackageConfig
@@ -12,28 +17,29 @@
 , GitRef(..)
 , packageDependencies
 , GhcOption
+, Section(..)
 , Library(..)
 , Executable(..)
 , SourceRepository(..)
 ) where
 
-import           Prelude ()
-import           Prelude.Compat
 import           Control.Applicative
 import           Control.Monad.Compat
-import           Data.List (nub, sort, (\\))
-import           Data.String
-import           Data.Maybe
-import           Data.Yaml
-import           GHC.Generics
+import           Data.Aeson.Types
+import           Data.Data
 import           Data.HashMap.Lazy (HashMap)
 import qualified Data.HashMap.Lazy as Map
+import           Data.List (nub, sort, (\\))
+import           Data.Maybe
+import           Data.String
 import           Data.Text (Text)
 import qualified Data.Text as T
-import           System.FilePath
+import           Data.Yaml
+import           GHC.Generics
+import           Prelude ()
+import           Prelude.Compat
 import           System.Directory
-import           Data.Data
-import           Data.Aeson.Types
+import           System.FilePath
 
 import           Hpack.Util
 
@@ -51,12 +57,20 @@
 hyphenize :: String -> String -> String
 hyphenize name = camelTo '-' . drop (length name)
 
+class HasFieldNames a where
+  fieldNames :: a -> [String]
+  default fieldNames :: Data a => a -> [String]
+  fieldNames a = map (hyphenize name) (constrFields constr)
+    where
+      constr = toConstr a
+      name = showConstr constr
+
 data CaptureUnknownFields a = CaptureUnknownFields {
   captureUnknownFieldsFields :: [String]
 , captureUnknownFieldsValue :: a
 } deriving (Eq, Show, Generic, Data, Typeable)
 
-instance (Data a, FromJSON a) => FromJSON (CaptureUnknownFields a) where
+instance (HasFieldNames a, FromJSON a) => FromJSON (CaptureUnknownFields a) where
   parseJSON v = captureUnknownFields <$> parseJSON v
     where
       captureUnknownFields a = case v of
@@ -64,37 +78,51 @@
           where
             unknown = keys \\ fields
             keys = map T.unpack (Map.keys o)
-            constr = toConstr a
-            name = showConstr constr
-            fields = map (hyphenize name) (constrFields constr)
+            fields = fieldNames a
         _ -> CaptureUnknownFields [] a
 
 data LibrarySection = LibrarySection {
-  librarySectionSourceDirs :: Maybe (List FilePath)
-, librarySectionExposedModules :: Maybe (List String)
+  librarySectionExposedModules :: Maybe (List String)
 , librarySectionOtherModules :: Maybe (List String)
-, librarySectionDependencies :: Maybe (List Dependency)
-, librarySectionDefaultExtensions :: Maybe (List String)
-, librarySectionGhcOptions :: Maybe (List GhcOption)
-, librarySectionCppOptions :: Maybe (List CppOption)
 } deriving (Eq, Show, Generic, Data, Typeable)
 
+instance HasFieldNames LibrarySection
+
 instance FromJSON LibrarySection where
   parseJSON = genericParseJSON_
 
 data ExecutableSection = ExecutableSection {
   executableSectionMain :: FilePath
-, executableSectionSourceDirs :: Maybe (List FilePath)
 , executableSectionOtherModules :: Maybe (List String)
-, executableSectionDependencies :: Maybe (List Dependency)
-, executableSectionDefaultExtensions :: Maybe (List String)
-, executableSectionGhcOptions :: Maybe (List GhcOption)
-, executableSectionCppOptions :: Maybe (List CppOption)
 } deriving (Eq, Show, Generic, Data, Typeable)
 
+instance HasFieldNames ExecutableSection
+
 instance FromJSON ExecutableSection where
   parseJSON = genericParseJSON_
 
+data WithCommonOptions a = WithCommonOptions a CommonOptions
+  deriving (Eq, Show, Generic, Data, Typeable)
+
+instance HasFieldNames a => HasFieldNames (WithCommonOptions a) where
+  fieldNames (WithCommonOptions a options) = (fieldNames a ++ fieldNames options) \\ ["config"] -- FIXME : test for removing "config"
+
+instance FromJSON a => FromJSON (WithCommonOptions a) where
+  parseJSON v = WithCommonOptions <$> parseJSON v <*> parseJSON v
+
+data CommonOptions = CommonOptions {
+  commonOptionsSourceDirs :: Maybe (List FilePath)
+, commonOptionsDependencies :: Maybe (List Dependency)
+, commonOptionsDefaultExtensions :: Maybe (List String)
+, commonOptionsGhcOptions :: Maybe (List GhcOption)
+, commonOptionsCppOptions :: Maybe (List CppOption)
+} deriving (Eq, Show, Generic, Data, Typeable)
+
+instance HasFieldNames CommonOptions
+
+instance FromJSON CommonOptions where
+  parseJSON = genericParseJSON_
+
 data PackageConfig = PackageConfig {
   packageConfigName :: Maybe String
 , packageConfigVersion :: Maybe String
@@ -110,21 +138,18 @@
 , packageConfigLicense :: Maybe String
 , packageConfigExtraSourceFiles :: Maybe (List FilePath)
 , packageConfigGithub :: Maybe Text
-, packageConfigSourceDirs :: Maybe (List FilePath)
-, packageConfigDependencies :: Maybe (List Dependency)
-, packageConfigDefaultExtensions :: Maybe (List String)
-, packageConfigGhcOptions :: Maybe (List GhcOption)
-, packageConfigCppOptions :: Maybe (List CppOption)
-, packageConfigLibrary :: Maybe (CaptureUnknownFields LibrarySection)
-, packageConfigExecutables :: Maybe (HashMap String (CaptureUnknownFields ExecutableSection))
-, packageConfigTests :: Maybe (HashMap String (CaptureUnknownFields ExecutableSection))
+, packageConfigLibrary :: Maybe (CaptureUnknownFields (WithCommonOptions LibrarySection))
+, packageConfigExecutables :: Maybe (HashMap String (CaptureUnknownFields (WithCommonOptions ExecutableSection)))
+, packageConfigTests :: Maybe (HashMap String (CaptureUnknownFields (WithCommonOptions ExecutableSection)))
 } deriving (Eq, Show, Generic, Data, Typeable)
 
+instance HasFieldNames PackageConfig
+
 packageDependencies :: Package -> [Dependency]
 packageDependencies Package{..} = nub . sort $
-     (concat $ concatMap executableDependencies packageExecutables)
-  ++ (concat $ concatMap executableDependencies packageTests)
-  ++ maybe [] (concat . libraryDependencies) packageLibrary
+     (concat $ concatMap sectionDependencies packageExecutables)
+  ++ (concat $ concatMap sectionDependencies packageTests)
+  ++ maybe [] (concat . sectionDependencies) packageLibrary
 
 instance FromJSON PackageConfig where
   parseJSON value = handleNullValues <$> genericParseJSON_ value
@@ -213,60 +238,55 @@
 , packageLicenseFile :: Maybe FilePath
 , packageExtraSourceFiles :: [FilePath]
 , packageSourceRepository :: Maybe SourceRepository
-, packageLibrary :: Maybe Library
-, packageExecutables :: [Executable]
-, packageTests :: [Executable]
+, packageLibrary :: Maybe (Section Library)
+, packageExecutables :: [Section Executable]
+, packageTests :: [Section Executable]
 } deriving (Eq, Show)
 
 data Library = Library {
-  librarySourceDirs :: [FilePath]
-, libraryExposedModules :: [String]
+  libraryExposedModules :: [String]
 , libraryOtherModules :: [String]
-, libraryDependencies :: [[Dependency]]
-, libraryDefaultExtensions :: [String]
-, libraryGhcOptions :: [GhcOption]
-, libraryCppOptions :: [CppOption]
 } deriving (Eq, Show)
 
 data Executable = Executable {
   executableName :: String
 , executableMain :: FilePath
-, executableSourceDirs :: [FilePath]
 , executableOtherModules :: [String]
-, executableDependencies :: [[Dependency]]
-, executableDefaultExtensions :: [String]
-, executableGhcOptions :: [GhcOption]
-, executableCppOptions :: [CppOption]
 } deriving (Eq, Show)
 
+data Section a = Section {
+  sectionData :: a
+, sectionSourceDirs :: [FilePath]
+, sectionDependencies :: [[Dependency]]
+, sectionDefaultExtensions :: [String]
+, sectionGhcOptions :: [GhcOption]
+, sectionCppOptions :: [CppOption]
+} deriving (Eq, Show, Functor, Foldable, Traversable)
+
 data SourceRepository = SourceRepository {
   sourceRepositoryUrl :: String
 , sourceRepositorySubdir :: Maybe String
 } deriving (Eq, Show)
 
-mkPackage :: (CaptureUnknownFields PackageConfig) -> IO ([String], Package)
-mkPackage (CaptureUnknownFields unknownFields PackageConfig{..}) = do
-  let dependencies = fromMaybeList packageConfigDependencies
-      sourceDirs = fromMaybeList packageConfigSourceDirs
-      defaultExtensions = fromMaybeList packageConfigDefaultExtensions
-      ghcOptions = fromMaybeList packageConfigGhcOptions
-      cppOptions = fromMaybeList packageConfigCppOptions
-      convert f = f sourceDirs dependencies defaultExtensions ghcOptions cppOptions
-
-  mLibrary <- mapM (convert toLibrary) mLibrarySection
-  executables <- convert toExecutables mExecutableSections
-  tests <- convert toExecutables mTestSections
+mkPackage :: (CaptureUnknownFields (WithCommonOptions PackageConfig)) -> IO ([String], Package)
+mkPackage (CaptureUnknownFields unknownFields (WithCommonOptions PackageConfig{..} globalOptions@CommonOptions{..})) = do
+  mLibrary <- mapM (toLibrary globalOptions) mLibrarySection
+  executables <- toExecutables globalOptions (map (fmap captureUnknownFieldsValue) executableSections)
+  tests <- toExecutables globalOptions (map (fmap captureUnknownFieldsValue) testsSections)
 
   name <- maybe (takeBaseName <$> getCurrentDirectory) return packageConfigName
 
   licenseFileExists <- doesFileExist "LICENSE"
 
   missingSourceDirs <- nub . sort <$> filterM (fmap not <$> doesDirectoryExist) (
-       maybe [] librarySourceDirs mLibrary
-    ++ concatMap executableSourceDirs executables
-    ++ concatMap executableSourceDirs tests
+       maybe [] sectionSourceDirs mLibrary
+    ++ concatMap sectionSourceDirs executables
+    ++ concatMap sectionSourceDirs tests
     )
 
+  (extraSourceFilesWarnings, extraSourceFiles) <-
+    expandGlobs (fromMaybeList packageConfigExtraSourceFiles)
+
   let package = Package {
         packageName = name
       , packageVersion = fromMaybe "0.0.0" packageConfigVersion
@@ -281,7 +301,7 @@
       , packageCopyright = fromMaybeList packageConfigCopyright
       , packageLicense = packageConfigLicense
       , packageLicenseFile = guard licenseFileExists >> Just "LICENSE"
-      , packageExtraSourceFiles = fromMaybeList packageConfigExtraSourceFiles
+      , packageExtraSourceFiles = extraSourceFiles
       , packageSourceRepository = sourceRepository
       , packageLibrary = mLibrary
       , packageExecutables = executables
@@ -291,26 +311,32 @@
       warnings =
            formatUnknownFields "package description" unknownFields
         ++ maybe [] (formatUnknownFields "library section") (captureUnknownFieldsFields <$> packageConfigLibrary)
-        ++ formatUnknownSectionFields "executable" packageConfigExecutables
-        ++ formatUnknownSectionFields "test" packageConfigTests
+        ++ formatUnknownSectionFields "executable" executableSections
+        ++ formatUnknownSectionFields "test" testsSections
         ++ formatMissingSourceDirs missingSourceDirs
+        ++ extraSourceFilesWarnings
 
   return (warnings, package)
   where
-    mLibrarySection :: Maybe LibrarySection
-    mLibrarySection = captureUnknownFieldsValue <$> packageConfigLibrary
+    executableSections :: [(String, CaptureUnknownFields (WithCommonOptions ExecutableSection))]
+    executableSections = toList packageConfigExecutables
 
-    mExecutableSections :: Maybe (HashMap String ExecutableSection)
-    mExecutableSections = fmap captureUnknownFieldsValue <$> packageConfigExecutables
+    testsSections :: [(String, CaptureUnknownFields (WithCommonOptions ExecutableSection))]
+    testsSections = toList packageConfigTests
 
-    mTestSections :: Maybe (HashMap String ExecutableSection)
-    mTestSections = fmap captureUnknownFieldsValue <$> packageConfigTests
+    toList :: Maybe (HashMap String a) -> [(String, a)]
+    toList = Map.toList . fromMaybe mempty
 
+    mLibrarySection :: Maybe (WithCommonOptions LibrarySection)
+    mLibrarySection = captureUnknownFieldsValue <$> packageConfigLibrary
+
+    formatUnknownFields :: String -> [String] -> [String]
     formatUnknownFields name = map f . sort
       where
         f field = "Ignoring unknown field " ++ show field ++ " in " ++ name
 
-    formatUnknownSectionFields sectionType = maybe [] (concatMap f . Map.toList . fmap captureUnknownFieldsFields)
+    formatUnknownSectionFields :: String -> [(String, CaptureUnknownFields a)] -> [String]
+    formatUnknownSectionFields sectionType = concatMap f . map (fmap captureUnknownFieldsFields)
       where
         f :: (String, [String]) -> [String]
         f (section, fields) = formatUnknownFields (sectionType ++ " section " ++ show section) fields
@@ -342,20 +368,53 @@
       where
         fromGithub = (++ "/issues") . sourceRepositoryUrl <$> sourceRepository
 
-toLibrary :: [FilePath] -> [Dependency] -> [String] -> [GhcOption] -> [CppOption] -> LibrarySection -> IO Library
-toLibrary globalSourceDirs globalDependencies globalDefaultExtensions globalGhcOptions globalCppOptions LibrarySection{..} = do
-  modules <- concat <$> mapM getModules sourceDirs
+toLibrary :: CommonOptions -> WithCommonOptions LibrarySection -> IO (Section Library)
+toLibrary globalOptions library = traverse fromLibrarySection section
+  where
+    section :: Section LibrarySection
+    section = toSection globalOptions library
 
-  let (exposedModules, otherModules) = determineModules modules librarySectionExposedModules librarySectionOtherModules
+    sourceDirs :: [FilePath]
+    sourceDirs = sectionSourceDirs section
 
-  return (Library sourceDirs exposedModules otherModules dependencies defaultExtensions ghcOptions cppOptions)
+    fromLibrarySection :: LibrarySection -> IO Library
+    fromLibrarySection LibrarySection{..} = do
+      modules <- concat <$> mapM getModules sourceDirs
+      let (exposedModules, otherModules) = determineModules modules librarySectionExposedModules librarySectionOtherModules
+      return (Library exposedModules otherModules)
+
+toExecutables :: CommonOptions -> [(String, WithCommonOptions ExecutableSection)] -> IO [Section Executable]
+toExecutables globalOptions executables = mapM toExecutable sections
   where
-    sourceDirs = globalSourceDirs ++ fromMaybeList librarySectionSourceDirs
-    dependencies = filter (not . null) [globalDependencies, fromMaybeList librarySectionDependencies]
-    defaultExtensions = globalDefaultExtensions ++ fromMaybeList librarySectionDefaultExtensions
-    ghcOptions = globalGhcOptions ++ fromMaybeList librarySectionGhcOptions
-    cppOptions = globalCppOptions ++ fromMaybeList librarySectionCppOptions
+    sections :: [(String, Section ExecutableSection)]
+    sections = map (fmap $ toSection globalOptions) executables
 
+    toExecutable :: (String, Section ExecutableSection) -> IO (Section Executable)
+    toExecutable (name, section) = traverse fromExecutableSection section
+      where
+        sourceDirs :: [FilePath]
+        sourceDirs = sectionSourceDirs section
+
+        fromExecutableSection :: ExecutableSection -> IO Executable
+        fromExecutableSection ExecutableSection{..} = do
+          modules <- maybe (filterMain . concat <$> mapM getModules sourceDirs) (return . fromList) executableSectionOtherModules
+          return (Executable name executableSectionMain modules)
+          where
+            filterMain :: [String] -> [String]
+            filterMain = maybe id (filter . (/=)) (toModule $ splitDirectories executableSectionMain)
+
+toSection :: CommonOptions -> WithCommonOptions a -> Section a
+toSection globalOptions (WithCommonOptions a options)
+  = Section a sourceDirs dependencies defaultExtensions ghcOptions cppOptions
+  where
+    sourceDirs = merge commonOptionsSourceDirs
+    defaultExtensions = merge commonOptionsDefaultExtensions
+    ghcOptions = merge commonOptionsGhcOptions
+    cppOptions = merge commonOptionsCppOptions
+    merge selector = fromMaybeList (selector globalOptions) ++ fromMaybeList (selector options)
+    getDependencies = fromMaybeList . commonOptionsDependencies
+    dependencies = filter (not . null) [getDependencies globalOptions, getDependencies options]
+
 determineModules :: [String] -> Maybe (List String) -> Maybe (List String) -> ([String], [String])
 determineModules modules mExposedModules mOtherModules = case (mExposedModules, mOtherModules) of
   (Nothing, Nothing) -> (modules, [])
@@ -373,23 +432,6 @@
   where
     toModules :: [[FilePath]] -> [String]
     toModules = catMaybes . map toModule
-
-toExecutables :: [FilePath] -> [Dependency] -> [String] -> [GhcOption] -> [CppOption] -> Maybe (HashMap String ExecutableSection) -> IO [Executable]
-toExecutables globalSourceDirs globalDependencies globalDefaultExtensions globalGhcOptions globalCppOptions executables =
-  (mapM toExecutable . Map.toList) (fromMaybe mempty executables)
-  where
-    toExecutable (name, ExecutableSection{..}) = do
-      modules <- maybe (filterMain . concat <$> mapM getModules sourceDirs) (return . fromList) executableSectionOtherModules
-      return $ Executable name executableSectionMain sourceDirs modules dependencies defaultExtensions ghcOptions cppOptions
-      where
-        dependencies = filter (not . null) [globalDependencies, fromMaybeList executableSectionDependencies]
-        sourceDirs = globalSourceDirs ++ fromMaybeList executableSectionSourceDirs
-        defaultExtensions = globalDefaultExtensions ++ fromMaybeList executableSectionDefaultExtensions
-        ghcOptions = globalGhcOptions ++ fromMaybeList executableSectionGhcOptions
-        cppOptions = globalCppOptions ++ fromMaybeList executableSectionCppOptions
-
-        filterMain :: [String] -> [String]
-        filterMain = maybe id (filter . (/=)) (toModule $ splitDirectories executableSectionMain)
 
 fromMaybeList :: Maybe (List a) -> [a]
 fromMaybeList = maybe [] fromList
diff --git a/src/Hpack/Run.hs b/src/Hpack/Run.hs
--- a/src/Hpack/Run.hs
+++ b/src/Hpack/Run.hs
@@ -1,15 +1,21 @@
-{-# LANGUAGE QuasiQuotes, RecordWildCards #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ViewPatterns #-}
 module Hpack.Run (
   run
 -- exported for testing
 , renderPackage
 , renderSourceRepository
+, formatDescription
 ) where
 
-import           Control.Applicative
+import           Prelude ()
+import           Prelude.Compat
+
 import           Control.Monad
+import           Data.Char
 import           Data.Maybe
-import           Data.List
+import           Data.List.Compat
 import           System.Exit.Compat
 
 import           Hpack.Util
@@ -77,7 +83,7 @@
         ("name", Just packageName)
       , ("version", Just packageVersion)
       , ("synopsis", packageSynopsis)
-      , ("description", (formatDescription <$> packageDescription))
+      , ("description", (formatDescription alignment <$> packageDescription))
       , ("category", packageCategory)
       , ("stability", packageStability)
       , ("homepage", packageHomepage)
@@ -99,11 +105,20 @@
     defaultFieldOrder :: [String]
     defaultFieldOrder = map fst fields
 
-    formatDescription = intercalate separator . intersperse "." . lines
-      where
-        n = max alignment $ length ("description: ")
-        separator = "\n" ++ replicate n ' '
+formatDescription :: Int -> String -> String
+formatDescription alignment description = case map emptyLineToDot $ lines description of
+  x : xs -> intercalate "\n" (x : map (indentation ++) xs)
+  [] -> ""
+  where
+    n = max alignment (length "description: ")
+    indentation = replicate n ' '
 
+    emptyLineToDot xs
+      | isEmptyLine xs = "."
+      | otherwise = xs
+
+    isEmptyLine = all isSpace
+
 renderSourceRepository :: SourceRepository -> String
 renderSourceRepository SourceRepository{..} = concat [
     "source-repository head\n"
@@ -112,47 +127,44 @@
   , maybe "" (("  subdir: " ++) . (++ "\n")) sourceRepositorySubdir
   ]
 
-renderExecutables :: [Executable] -> [String]
+renderExecutables :: [Section Executable] -> [String]
 renderExecutables = map renderExecutable
 
-renderExecutable :: Executable -> String
-renderExecutable executable@Executable{..} =
+renderExecutable :: Section Executable -> String
+renderExecutable section@(sectionData -> Executable{..}) =
      "executable "
   ++ executableName ++ "\n"
-  ++ renderExecutableSection executable
+  ++ renderExecutableSection section
 
-renderTests :: [Executable] -> [String]
+renderTests :: [Section Executable] -> [String]
 renderTests = map renderTest
 
-renderTest :: Executable -> String
-renderTest executable@Executable{..} =
+renderTest :: Section Executable -> String
+renderTest section@(sectionData -> Executable{..}) =
      "test-suite " ++ executableName ++ "\n"
   ++ "  type: exitcode-stdio-1.0\n"
-  ++ renderExecutableSection executable
+  ++ renderExecutableSection section
 
-renderExecutableSection :: Executable -> String
-renderExecutableSection Executable{..} =
-     renderSourceDirs executableSourceDirs
-  ++ "  main-is: " ++ executableMain ++ "\n"
-  ++ renderOtherModules executableOtherModules
-  ++ renderDependencies executableDependencies
-  ++ renderDefaultExtensions executableDefaultExtensions
-  ++ renderGhcOptions executableGhcOptions
-  ++ renderCppOptions executableCppOptions
-  ++ "  default-language: Haskell2010\n"
+renderExecutableSection :: Section Executable -> String
+renderExecutableSection section@(sectionData -> Executable{..}) =
+     "  main-is: " ++ executableMain ++ "\n"
+  ++ renderSection section
 
-renderLibrary :: Library -> String
-renderLibrary Library{..} =
+renderLibrary :: Section Library -> String
+renderLibrary section@(sectionData -> Library{..}) =
     "library\n"
-  ++ renderSourceDirs librarySourceDirs
   ++ renderExposedModules libraryExposedModules
   ++ renderOtherModules libraryOtherModules
-  ++ renderDependencies libraryDependencies
-  ++ renderDefaultExtensions libraryDefaultExtensions
-  ++ renderGhcOptions libraryGhcOptions
-  ++ renderCppOptions libraryCppOptions
-  ++ "  default-language: Haskell2010\n"
+  ++ renderSection section
 
+renderSection :: Section a -> String
+renderSection Section{..} =
+     renderSourceDirs sectionSourceDirs
+  ++ renderDependencies sectionDependencies
+  ++ renderDefaultExtensions sectionDefaultExtensions
+  ++ renderGhcOptions sectionGhcOptions
+  ++ renderCppOptions sectionCppOptions
+  ++ "  default-language: Haskell2010\n"
 
 renderSourceDirs :: [String] -> String
 renderSourceDirs dirs
diff --git a/src/Hpack/Util.hs b/src/Hpack/Util.hs
--- a/src/Hpack/Util.hs
+++ b/src/Hpack/Util.hs
@@ -6,6 +6,7 @@
 , tryReadFile
 , sniffAlignment
 , extractFieldOrderHint
+, expandGlobs
 
 -- exported for testing
 , splitField
@@ -20,6 +21,7 @@
 import           Data.List
 import           System.Directory
 import           System.FilePath
+import           System.FilePath.Glob
 import           Data.Data
 
 import           Data.Aeson.Types
@@ -88,3 +90,23 @@
   where
     isNameChar = (`elem` nameChars)
     nameChars = ['a'..'z'] ++ ['A'..'Z'] ++ "-"
+
+expandGlobs :: [String] -> IO ([String], [FilePath])
+expandGlobs patterns = do
+  files <- (fst <$> globDir compiledPatterns ".") >>= mapM removeDirectories
+  let warnings = [warn pattern | ([], pattern) <- zip files patterns]
+  return (warnings, combineResults files)
+  where
+    combineResults = nub . map (makeRelative ".") . sort . concat
+    warn pattern = "Specified pattern " ++ show pattern ++ " for extra-source-files does not match any files"
+    compiledPatterns = map (compileWith options) patterns
+    removeDirectories = filterM doesFileExist
+    options = CompOptions {
+        characterClasses = False
+      , characterRanges = False
+      , numberRanges = False
+      , wildcards = True
+      , recursiveWildcards = True
+      , pathSepInRanges = False
+      , errorRecovery = True
+      }
diff --git a/test/Hpack/ConfigSpec.hs b/test/Hpack/ConfigSpec.hs
--- a/test/Hpack/ConfigSpec.hs
+++ b/test/Hpack/ConfigSpec.hs
@@ -2,12 +2,12 @@
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE QuasiQuotes #-}
 module Hpack.ConfigSpec (
-  main
-, spec
+  spec
 
 , package
 , executable
 , library
+, section
 ) where
 
 import           Helper
@@ -18,18 +18,18 @@
 
 import           Hpack.Config
 
-main :: IO ()
-main = hspec spec
-
 package :: Package
 package = Package "foo" "0.0.0" Nothing Nothing Nothing Nothing Nothing Nothing [] [] [] Nothing Nothing [] Nothing Nothing [] []
 
 executable :: String -> String -> Executable
-executable name main_ = Executable name main_ [] [] [] [] [] []
+executable name main_ = Executable name main_ []
 
 library :: Library
-library = Library [] [] [] [] [] [] []
+library = Library [] []
 
+section :: a -> Section a
+section a = Section a [] [] [] [] []
+
 spec :: Spec
 spec = do
   describe "parseJSON" $ do
@@ -219,6 +219,8 @@
           - CHANGES.markdown
           - README.markdown
         |]
+      touch "CHANGES.markdown"
+      touch "README.markdown"
       Right (_, c) <- readPackageConfig "package.yaml"
       packageExtraSourceFiles c `shouldBe` ["CHANGES.markdown", "README.markdown"]
 
@@ -257,9 +259,9 @@
         |]
       Right (_, c) <- readPackageConfig "package.yaml"
       c `shouldBe` package {
-          packageLibrary = Just library {libraryCppOptions = ["-DFOO", "-DLIB"]}
-        , packageExecutables = [(executable "foo" "Main.hs") {executableCppOptions = ["-DFOO", "-DFOO"]}]
-        , packageTests = [(executable "spec" "Spec.hs") {executableCppOptions = ["-DFOO", "-DTEST"]}]
+          packageLibrary = Just (section library) {sectionCppOptions = ["-DFOO", "-DLIB"]}
+        , packageExecutables = [(section $ executable "foo" "Main.hs") {sectionCppOptions = ["-DFOO", "-DFOO"]}]
+        , packageTests = [(section $ executable "spec" "Spec.hs") {sectionCppOptions = ["-DFOO", "-DTEST"]}]
         }
 
     context "when reading library section" $ do
@@ -283,7 +285,7 @@
               - bar
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageLibrary c `shouldBe` Just library {librarySourceDirs = ["foo", "bar"]}
+        packageLibrary c `shouldBe` Just (section library) {sectionSourceDirs = ["foo", "bar"]}
 
       it "accepts default-extensions" $ do
         writeFile "package.yaml" [i|
@@ -293,7 +295,7 @@
               - Bar
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageLibrary c `shouldBe` Just library {libraryDefaultExtensions = ["Foo", "Bar"]}
+        packageLibrary c `shouldBe` Just (section library) {sectionDefaultExtensions = ["Foo", "Bar"]}
 
       it "accepts global default-extensions" $ do
         writeFile "package.yaml" [i|
@@ -303,7 +305,7 @@
           library: {}
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageLibrary c `shouldBe` Just library {libraryDefaultExtensions = ["Foo", "Bar"]}
+        packageLibrary c `shouldBe` Just (section library) {sectionDefaultExtensions = ["Foo", "Bar"]}
 
       it "accepts global source-dirs" $ do
         writeFile "package.yaml" [i|
@@ -313,7 +315,7 @@
           library: {}
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageLibrary c `shouldBe` Just library {librarySourceDirs = ["foo", "bar"]}
+        packageLibrary c `shouldBe` Just (section library) {sectionSourceDirs = ["foo", "bar"]}
 
       it "allows to specify exposed-modules" $ do
         writeFile "package.yaml" [i|
@@ -324,7 +326,7 @@
         touch "src/Foo.hs"
         touch "src/Bar.hs"
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageLibrary c `shouldBe` Just library {librarySourceDirs = ["src"], libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}
+        packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}) {sectionSourceDirs = ["src"]}
 
       it "allows to specify other-modules" $ do
         writeFile "package.yaml" [i|
@@ -335,7 +337,7 @@
         touch "src/Foo.hs"
         touch "src/Bar.hs"
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageLibrary c `shouldBe` Just library {librarySourceDirs = ["src"], libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}
+        packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}) {sectionSourceDirs = ["src"]}
 
       it "allows to specify both exposed-modules and other-modules" $ do
         writeFile "package.yaml" [i|
@@ -346,7 +348,7 @@
           |]
         touch "src/Baz.hs"
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageLibrary c `shouldBe` Just library {librarySourceDirs = ["src"], libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}
+        packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}) {sectionSourceDirs = ["src"]}
 
       context "when neither exposed-module nor other-module are specified" $ do
         it "exposes all modules" $ do
@@ -357,7 +359,7 @@
           touch "src/Foo.hs"
           touch "src/Bar.hs"
           Right (_, c) <- readPackageConfig "package.yaml"
-          packageLibrary c `shouldBe` Just library {librarySourceDirs = ["src"], libraryExposedModules = ["Bar", "Foo"]}
+          packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Bar", "Foo"]}) {sectionSourceDirs = ["src"]}
 
     context "when reading executable section" $ do
       it "warns on unknown fields" $ do
@@ -381,7 +383,7 @@
               main: driver/Main.hs
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageExecutables c `shouldBe` [executable "foo" "driver/Main.hs"]
+        packageExecutables c `shouldBe` [section $ executable "foo" "driver/Main.hs"]
 
       it "accepts source-dirs" $ do
         writeFile "package.yaml" [i|
@@ -393,7 +395,7 @@
                 - bar
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageExecutables c `shouldBe` [(executable "foo" "Main.hs") {executableSourceDirs = ["foo", "bar"]}]
+        packageExecutables c `shouldBe` [(section $ executable "foo" "Main.hs") {sectionSourceDirs = ["foo", "bar"]}]
 
       it "accepts global source-dirs" $ do
         writeFile "package.yaml" [i|
@@ -405,7 +407,7 @@
               main: Main.hs
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageExecutables c `shouldBe` [(executable "foo" "Main.hs") {executableSourceDirs = ["foo", "bar"]}]
+        packageExecutables c `shouldBe` [(section $ executable "foo" "Main.hs") {sectionSourceDirs = ["foo", "bar"]}]
 
       it "infers other-modules" $ do
         touch "src/Main.hs"
@@ -419,7 +421,7 @@
               source-dirs: src
           |]
         Right (_, [r]) <- (fmap . fmap) packageExecutables <$> readPackageConfig "package.yaml"
-        executableOtherModules r `shouldBe` ["Bar", "Baz", "Foo"]
+        executableOtherModules (sectionData r) `shouldBe` ["Bar", "Baz", "Foo"]
 
       it "allows to specify other-modules" $ do
         touch "src/Foo.hs"
@@ -432,7 +434,7 @@
               other-modules: Baz
           |]
         Right (_, [r]) <- (fmap . fmap) packageExecutables <$> readPackageConfig "package.yaml"
-        executableOtherModules r `shouldBe` ["Baz"]
+        executableOtherModules (sectionData r) `shouldBe` ["Baz"]
 
       it "accepts default-extensions" $ do
         writeFile "package.yaml" [i|
@@ -444,7 +446,7 @@
                 - Bar
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageExecutables c `shouldBe` [(executable "foo" "driver/Main.hs") {executableDefaultExtensions = ["Foo", "Bar"]}]
+        packageExecutables c `shouldBe` [(section $ executable "foo" "driver/Main.hs") {sectionDefaultExtensions = ["Foo", "Bar"]}]
 
       it "accepts global default-extensions" $ do
         writeFile "package.yaml" [i|
@@ -456,7 +458,7 @@
               main: driver/Main.hs
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        packageExecutables c `shouldBe` [(executable "foo" "driver/Main.hs") {executableDefaultExtensions = ["Foo", "Bar"]}]
+        packageExecutables c `shouldBe` [(section $ executable "foo" "driver/Main.hs") {sectionDefaultExtensions = ["Foo", "Bar"]}]
 
       it "accepts GHC options" $ do
         writeFile "package.yaml" [i|
@@ -466,7 +468,7 @@
               ghc-options: -Wall
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        c `shouldBe` package {packageExecutables = [(executable "foo" "driver/Main.hs") {executableGhcOptions = ["-Wall"]}]}
+        c `shouldBe` package {packageExecutables = [(section $ executable "foo" "driver/Main.hs") {sectionGhcOptions = ["-Wall"]}]}
 
       it "accepts global GHC options" $ do
         writeFile "package.yaml" [i|
@@ -476,7 +478,7 @@
               main: driver/Main.hs
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        c `shouldBe` package {packageExecutables = [(executable "foo" "driver/Main.hs") {executableGhcOptions = ["-Wall"]}]}
+        c `shouldBe` package {packageExecutables = [(section $ executable "foo" "driver/Main.hs") {sectionGhcOptions = ["-Wall"]}]}
 
     context "when reading test section" $ do
       it "warns on unknown fields" $ do
@@ -500,7 +502,7 @@
               main: test/Spec.hs
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        c `shouldBe` package {packageTests = [executable "spec" "test/Spec.hs"]}
+        c `shouldBe` package {packageTests = [section $ executable "spec" "test/Spec.hs"]}
 
       it "accepts single dependency" $ do
         writeFile "package.yaml" [i|
@@ -510,7 +512,7 @@
               dependencies: hspec
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        c `shouldBe` package {packageTests = [(executable "spec" "test/Spec.hs") {executableDependencies = [["hspec"]]}]}
+        c `shouldBe` package {packageTests = [(section $ executable "spec" "test/Spec.hs") {sectionDependencies = [["hspec"]]}]}
 
       it "accepts list of dependencies" $ do
         writeFile "package.yaml" [i|
@@ -522,7 +524,7 @@
                 - QuickCheck
           |]
         Right (_, c) <- readPackageConfig "package.yaml"
-        c `shouldBe` package {packageTests = [(executable "spec" "test/Spec.hs") {executableDependencies = [["hspec", "QuickCheck"]]}]}
+        c `shouldBe` package {packageTests = [(section $ executable "spec" "test/Spec.hs") {sectionDependencies = [["hspec", "QuickCheck"]]}]}
 
       context "when both global and section specific dependencies are specified" $ do
         it "combines dependencies" $ do
@@ -536,7 +538,7 @@
                 dependencies: hspec
             |]
           Right (_, c) <- readPackageConfig "package.yaml"
-          c `shouldBe` package {packageTests = [(executable "spec" "test/Spec.hs") {executableDependencies = [["base"], ["hspec"]]}]}
+          c `shouldBe` package {packageTests = [(section $ executable "spec" "test/Spec.hs") {sectionDependencies = [["base"], ["hspec"]]}]}
 
     context "when a specified source directory does not exist" $ do
       it "warns" $ do
diff --git a/test/Hpack/RunSpec.hs b/test/Hpack/RunSpec.hs
--- a/test/Hpack/RunSpec.hs
+++ b/test/Hpack/RunSpec.hs
@@ -1,15 +1,13 @@
 {-# LANGUAGE OverloadedStrings #-}
-module Hpack.RunSpec (main, spec) where
+module Hpack.RunSpec (spec) where
 
 import           Test.Hspec
+import           Data.List
 
-import           Hpack.ConfigSpec hiding (main, spec)
+import           Hpack.ConfigSpec hiding (spec)
 import           Hpack.Config
 import           Hpack.Run
 
-main :: IO ()
-main = hspec spec
-
 spec :: Spec
 spec = do
   describe "renderPackage" $ do
@@ -30,7 +28,7 @@
         ]
 
     it "includes description" $ do
-      renderPackage 0 [] package {packageDescription = Just "foo\nbar\n"} `shouldBe` unlines [
+      renderPackage 0 [] package {packageDescription = Just "foo\n\nbar\n"} `shouldBe` unlines [
           "name: foo"
         , "version: 0.0.0"
         , "description: foo"
@@ -41,7 +39,7 @@
         ]
 
     it "aligns description" $ do
-      renderPackage 16 [] package {packageDescription = Just "foo\nbar\n"} `shouldBe` unlines [
+      renderPackage 16 [] package {packageDescription = Just "foo\n\nbar\n"} `shouldBe` unlines [
           "name:           foo"
         , "version:        0.0.0"
         , "description:    foo"
@@ -110,7 +108,7 @@
 
     context "when rendering executable section" $ do
       it "includes dependencies" $ do
-        renderPackage 0 [] package {packageExecutables = [(executable "foo" "Main.hs") {executableDependencies = [["foo", "bar"], ["foo", "baz"]]}]} `shouldBe` unlines [
+        renderPackage 0 [] package {packageExecutables = [(section $ executable "foo" "Main.hs") {sectionDependencies = [["foo", "bar"], ["foo", "baz"]]}]} `shouldBe` unlines [
             "name: foo"
           , "version: 0.0.0"
           , "build-type: Simple"
@@ -128,7 +126,7 @@
           ]
 
       it "includes GHC options" $ do
-        renderPackage 0 [] package {packageExecutables = [(executable "foo" "Main.hs") {executableGhcOptions = ["-Wall", "-Werror"]}]} `shouldBe` unlines [
+        renderPackage 0 [] package {packageExecutables = [(section $ executable "foo" "Main.hs") {sectionGhcOptions = ["-Wall", "-Werror"]}]} `shouldBe` unlines [
             "name: foo"
           , "version: 0.0.0"
           , "build-type: Simple"
@@ -139,6 +137,41 @@
           , "  ghc-options: -Wall -Werror"
           , "  default-language: Haskell2010"
           ]
+
+  describe "formatDescription" $ do
+    it "formats description" $ do
+      let description = unlines [
+              "foo"
+            , "bar"
+            ]
+      "description: " ++ formatDescription 0 description `shouldBe` intercalate "\n" [
+          "description: foo"
+        , "             bar"
+        ]
+
+    it "takes specified alignment into account" $ do
+      let description = unlines [
+              "foo"
+            , "bar"
+            , "baz"
+            ]
+      "description:   " ++ formatDescription 15 description `shouldBe` intercalate "\n" [
+          "description:   foo"
+        , "               bar"
+        , "               baz"
+        ]
+
+    it "formats empty lines" $ do
+      let description = unlines [
+              "foo"
+            , "   "
+            , "bar"
+            ]
+      "description: " ++ formatDescription 0 description `shouldBe` intercalate "\n" [
+          "description: foo"
+        , "             ."
+        , "             bar"
+        ]
 
   describe "renderSourceRepository" $ do
     it "renders source-repository without subdir correctly" $ do
diff --git a/test/Hpack/UtilSpec.hs b/test/Hpack/UtilSpec.hs
--- a/test/Hpack/UtilSpec.hs
+++ b/test/Hpack/UtilSpec.hs
@@ -3,6 +3,8 @@
 
 import           Helper
 import           Data.Aeson
+import           Data.List
+import           System.Directory
 
 import           Hpack.Util
 
@@ -100,3 +102,65 @@
 
     it "rejects invalid fields" $ do
       splitField "foo bar" `shouldBe` Nothing
+
+  describe "expandGlobs" $ around_ inTempDirectory $ do
+    it "accepts simple files" $ do
+      touch "foo.js"
+      expandGlobs ["foo.js"] `shouldReturn` ([], ["foo.js"])
+
+    it "removes duplicates" $ do
+      touch "foo.js"
+      expandGlobs ["foo.js", "*.js"] `shouldReturn` ([], ["foo.js"])
+
+    it "rejects directories" $ do
+      touch "foo"
+      createDirectory "bar"
+      expandGlobs ["*"] `shouldReturn` ([], ["foo"])
+
+    it "rejects character ranges" $ do
+      touch "foo1"
+      touch "foo2"
+      touch "foo[1,2]"
+      expandGlobs ["foo[1,2]"] `shouldReturn` ([], ["foo[1,2]"])
+
+    context "when expanding *" $ do
+      it "expands by extension" $ do
+        let files = [
+                "files/foo.js"
+              , "files/bar.js"
+              , "files/baz.js"]
+        mapM_ touch files
+        touch "files/foo.hs"
+        expandGlobs ["files/*.js"] `shouldReturn` ([], sort files)
+
+      it "rejects dot-files" $ do
+        touch "foo/bar"
+        touch "foo/.baz"
+        expandGlobs ["foo/*"] `shouldReturn` ([], ["foo/bar"])
+
+      it "accepts dot-files when explicitly asked to" $ do
+        touch "foo/bar"
+        touch "foo/.baz"
+        expandGlobs ["foo/.*"] `shouldReturn` ([], ["foo/.baz"])
+
+      it "matches at most one directory component" $ do
+        touch "foo/bar/baz.js"
+        touch "foo/bar.js"
+        expandGlobs ["*/*.js"] `shouldReturn` ([], ["foo/bar.js"])
+
+    context "when expanding **" $ do
+      it "matches arbitrary many directory components" $ do
+        let file = "foo/bar/baz.js"
+        touch file
+        expandGlobs ["**/*.js"] `shouldReturn` ([], [file])
+
+    context "when a pattern does not match anything" $ do
+      it "warns" $ do
+        expandGlobs ["foo"] `shouldReturn`
+          (["Specified pattern \"foo\" for extra-source-files does not match any files"], [])
+
+    context "when a pattern only matches a directory" $ do
+      it "warns" $ do
+        createDirectory "foo"
+        expandGlobs ["foo"] `shouldReturn`
+          (["Specified pattern \"foo\" for extra-source-files does not match any files"], [])
