Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generate-file #437

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## Changes in 0.34.5
- Add support for `generate-file`

## Changes in 0.34.4
- Render `default-extensions` / `other-extensions` line-separated
- Compatibility with `Cabal-3.4.0.0`
Expand Down
57 changes: 46 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,41 @@ at the Singapore Haskell meetup: http://typeful.net/talks/hpack

## Documentation

<!--ts-->
* [hpack: A modern format for Haskell packages](#hpack-a-modern-format-for-haskell-packages)
* [Design principles](#design-principles)
* [Tool integration](#tool-integration)
* [There is no user guide](#there-is-no-user-guide)
* [Examples](#examples)
* [Documentation](#documentation)
* [Handling of Paths_ modules](#handling-of-paths_-modules)
* [Quick-reference](#quick-reference)
* [Top-level fields](#top-level-fields)
* [cabal-version](#cabal-version)
* [Defaults](#defaults)
* [Custom setup](#custom-setup)
* [Common fields](#common-fields)
* [Library fields](#library-fields)
* [Executable fields](#executable-fields)
* [Test fields](#test-fields)
* [Benchmark fields](#benchmark-fields)
* [Flags](#flags)
* [Dependencies](#dependencies)
* [Conditionals](#conditionals)
* [File globbing](#file-globbing)
* [Passing things to Cabal verbatim](#passing-things-to-cabal-verbatim)
* [Objects](#objects)
* [Strings](#strings)
* [Lists of objects and strings](#lists-of-objects-and-strings)
* [Not repeating yourself](#not-repeating-yourself)
* [Vim integration](#vim-integration)
* [Stack support](#stack-support)
* [Binaries for use on Travis CI](#binaries-for-use-on-travis-ci)

<!-- Added by: sol, at: Fri 19 Feb 2021 10:31:47 PM +07 -->

<!--te-->

### Handling of `Paths_` modules

Cabal generates a `Paths_` module for every package. By default Hpack adds
Expand Down Expand Up @@ -110,7 +145,7 @@ verbatim:
cabal-version: 2.2
```

#### <a name="defaults"></a>Defaults
#### Defaults

Hpack allows the inclusion of [common fields](#common-fields) from a file on
GitHub or a local file.
Expand Down Expand Up @@ -167,13 +202,13 @@ this reason it is recommended to only use tags as Git references.
defaults file, then you can achieve this by adding that file to the cache
manually.

#### <a name="custom-setup"></a>Custom setup
#### Custom setup

| Hpack | Cabal | Default | Notes | Example |
| --- | --- | --- | --- | --- |
| `dependencies` | `setup-depends` | | Implies `build-type: Custom` | |

#### <a name="common-fields"></a>Common fields
#### Common fields

These fields can be specified top-level or on a per section basis; top-level
values are merged with per section values.
Expand Down Expand Up @@ -249,7 +284,7 @@ This is done to allow compatibility with a wider range of `Cabal` versions.
**Note:** Unlike `Cabal`, Hpack does not accept system executables as
`build-tools`. Use `system-build-tools` if you need this.

#### <a name="library-fields"></a>Library fields
#### Library fields

| Hpack | Cabal | Default | Notes |
| --- | --- | --- | --- |
Expand All @@ -263,7 +298,7 @@ This is done to allow compatibility with a wider range of `Cabal` versions.
| `signatures` | · | | |
| | `default-language` | `Haskell2010` | |

#### <a name="executable-fields"></a>Executable fields
#### Executable fields

| Hpack | Cabal | Default | Notes |
| --- | --- | --- | --- |
Expand All @@ -272,7 +307,7 @@ This is done to allow compatibility with a wider range of `Cabal` versions.
| `generated-other-modules` | | | Added to `other-modules` and `autogen-modules`. Since `0.23.0`.
| | `default-language` | `Haskell2010` | |

#### <a name="test-fields"></a>Test fields
#### Test fields

| Hpack | Cabal | Default | Notes |
| --- | --- | --- | --- |
Expand All @@ -282,7 +317,7 @@ This is done to allow compatibility with a wider range of `Cabal` versions.
| `generated-other-modules` | | | Added to `other-modules` and `autogen-modules`. Since `0.23.0`.
| | `default-language` | `Haskell2010` | |

#### <a name="benchmark-fields"></a>Benchmark fields
#### Benchmark fields

| Hpack | Cabal | Default | Notes |
| --- | --- | --- | --- |
Expand All @@ -292,15 +327,15 @@ This is done to allow compatibility with a wider range of `Cabal` versions.
| `generated-other-modules` | | | Added to `other-modules` and `autogen-modules`. Since `0.23.0`.
| | `default-language` | `Haskell2010` | |

#### <a name="flags"></a>Flags
#### Flags

| Hpack | Cabal | Default | Notes |
| --- | --- | --- | --- |
| `description` | · | | Optional |
| `manual` | · | | Required (unlike Cabal) |
| `default` | · | | Required (unlike Cabal) |

#### <a name="dependencies"></a> Dependencies
#### Dependencies

Dependencies can be specified as either a list or an object. These are
equivalent:
Expand Down Expand Up @@ -375,7 +410,7 @@ imported!

`mixin` was added in version `0.31.0`.

#### <a name="conditionals"></a> Conditionals
#### Conditionals

Conditionals with no else branch:

Expand Down Expand Up @@ -419,7 +454,7 @@ becomes
**Note:** Conditionals with `condition: false` are omitted from the generated
`.cabal` file.

### <a name="file-globbing"></a>File globbing
### File globbing

At place where you can specify a list of files you can also use glob patterns.
Glob patterns and ordinary file names can be freely mixed, e.g.:
Expand Down
2 changes: 1 addition & 1 deletion hpack.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack

name: hpack
version: 0.34.4
version: 0.35.0
synopsis: A modern format for Haskell packages
description: See README at <https://github.com/sol/hpack#readme>
category: Development
Expand Down
2 changes: 1 addition & 1 deletion package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: hpack
version: 0.34.4
version: 0.35.0
synopsis: A modern format for Haskell packages
description: See README at <https://github.com/sol/hpack#readme>
maintainer: Simon Hengel <[email protected]>
Expand Down
9 changes: 8 additions & 1 deletion src/Hpack.hs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ hpackResult = hpackResultWithVersion version

hpackResultWithVersion :: Version -> Options -> IO Result
hpackResultWithVersion v (Options options force generateHashStrategy toStdout) = do
DecodeResult pkg (lines -> cabalVersion) cabalFileName warnings <- readPackageConfig options >>= either die return
DecodeResult pkg (lines -> cabalVersion) cabalFileName files warnings <- readPackageConfig options >>= either die return
mExistingCabalFile <- readCabalFile cabalFileName
let
newCabalFile = makeCabalFile generateHashStrategy mExistingCabalFile cabalVersion v pkg
Expand All @@ -205,6 +205,13 @@ hpackResultWithVersion v (Options options force generateHashStrategy toStdout) =
Generated -> writeCabalFile options toStdout cabalFileName newCabalFile
_ -> return ()

let generateFiles = mapM_ (uncurry ensureFile) files
case status of
Generated -> generateFiles
OutputUnchanged -> generateFiles
AlreadyGeneratedByNewerHpack -> return ()
ExistingCabalFileWasModifiedManually -> return ()

return Result {
resultWarnings = warnings
, resultCabalFile = cabalFileName
Expand Down
63 changes: 45 additions & 18 deletions src/Hpack/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,14 @@ data CommonOptions cSources cxxSources jsSources a = CommonOptions {
, commonOptionsBuildTools :: Maybe BuildTools
, commonOptionsSystemBuildTools :: Maybe SystemBuildTools
, commonOptionsVerbatim :: Maybe (List Verbatim)
, commonOptionsGenerateFile :: Maybe (List GenerateFile)
} deriving (Functor, Generic)

data GenerateFile = GenerateFile {
generateFileName :: FilePath
, generateFileContents :: String
} deriving (Generic, FromValue)

type ParseCommonOptions = CommonOptions ParseCSources ParseCxxSources ParseJsSources
instance FromValue a => FromValue (ParseCommonOptions a)

Expand Down Expand Up @@ -325,6 +331,7 @@ instance (Semigroup cSources, Semigroup cxxSources, Semigroup jsSources, Monoid
, commonOptionsBuildTools = Nothing
, commonOptionsSystemBuildTools = Nothing
, commonOptionsVerbatim = Nothing
, commonOptionsGenerateFile = Nothing
}
mappend = (<>)

Expand Down Expand Up @@ -356,6 +363,7 @@ instance (Semigroup cSources, Semigroup cxxSources, Semigroup jsSources) => Semi
, commonOptionsBuildTools = commonOptionsBuildTools a <> commonOptionsBuildTools b
, commonOptionsSystemBuildTools = commonOptionsSystemBuildTools b <> commonOptionsSystemBuildTools a
, commonOptionsVerbatim = commonOptionsVerbatim a <> commonOptionsVerbatim b
, commonOptionsGenerateFile = commonOptionsGenerateFile a <> commonOptionsGenerateFile b
}

type ParseCSources = Maybe (List FilePath)
Expand Down Expand Up @@ -644,6 +652,7 @@ data DecodeResult = DecodeResult {
decodeResultPackage :: Package
, decodeResultCabalVersion :: String
, decodeResultCabalFile :: FilePath
, decodeResultGenerateFiles :: [(FilePath, String)]
, decodeResultWarnings :: [String]
} deriving (Eq, Show)

Expand All @@ -656,8 +665,16 @@ readPackageConfig (DecodeOptions programName file mUserDataDir readValue) = runE
userDataDir <- liftIO $ maybe (getAppUserDataDirectory "hpack") return mUserDataDir
toPackage programName userDataDir dir config
where
addCabalFile :: ((Package, String), [String]) -> DecodeResult
addCabalFile ((pkg, cabalVersion), warnings) = DecodeResult pkg cabalVersion (takeDirectory_ file </> (packageName pkg ++ ".cabal")) warnings
addCabalFile :: ((Package, String, [GenerateFile]), [String]) -> DecodeResult
addCabalFile ((pkg, cabalVersion, generateFiles), warnings) = DecodeResult {
decodeResultPackage = pkg
, decodeResultCabalVersion = cabalVersion
, decodeResultCabalFile = addPackageDir (packageName pkg ++ ".cabal")
, decodeResultGenerateFiles = map (first addPackageDir . (generateFileName &&& generateFileContents)) $ nubOn generateFileName $ reverse generateFiles
, decodeResultWarnings = warnings
}

addPackageDir = (takeDirectory_ file </>)

takeDirectory_ :: FilePath -> FilePath
takeDirectory_ p
Expand Down Expand Up @@ -997,11 +1014,14 @@ type ConfigWithDefaults = Product
type CommonOptionsWithDefaults a = Product DefaultsConfig (CommonOptions ParseCSources ParseCxxSources ParseJsSources a)
type WithCommonOptionsWithDefaults a = Product DefaultsConfig (WithCommonOptions ParseCSources ParseCxxSources ParseJsSources a)

toPackage :: ProgramName -> FilePath -> FilePath -> ConfigWithDefaults -> Warnings (Errors IO) (Package, String)
toPackage :: ProgramName -> FilePath -> FilePath -> ConfigWithDefaults -> Warnings (Errors IO) (Package, String, [GenerateFile])
toPackage programName userDataDir dir =
expandDefaultsInConfig programName userDataDir dir
>=> traverseConfig (expandForeignSources dir)
>=> toPackage_ dir
>=> runGenerateFilesWithWarnings . toPackage_ dir

runGenerateFilesWithWarnings :: Functor m => GenerateFilesWithWarnings m (a, b) -> Warnings m (a, b, [GenerateFile])
runGenerateFilesWithWarnings = mapWriterT (fmap $ \ ((a, b), c) -> ((a, b, lefts c), rights c))

expandDefaultsInConfig
:: ProgramName
Expand Down Expand Up @@ -1090,19 +1110,19 @@ toExecutableMap name executables mExecutable = do

type GlobalOptions = CommonOptions CSources CxxSources JsSources Empty

toPackage_ :: MonadIO m => FilePath -> Product GlobalOptions (PackageConfig CSources CxxSources JsSources) -> Warnings m (Package, String)
toPackage_ :: MonadIO m => FilePath -> Product GlobalOptions (PackageConfig CSources CxxSources JsSources) -> GenerateFilesWithWarnings m (Package, String)
toPackage_ dir (Product g PackageConfig{..}) = do
executableMap <- toExecutableMap packageName_ packageConfigExecutables packageConfigExecutable
executableMap <- liftWarnings $ toExecutableMap packageName_ packageConfigExecutables packageConfigExecutable
let
globalVerbatim = commonOptionsVerbatim g
globalOptions = g {commonOptionsVerbatim = Nothing}

executableNames = maybe [] Map.keys executableMap

toSect :: (Monad m, Monoid a) => WithCommonOptions CSources CxxSources JsSources a -> Warnings m (Section a)
toSect :: (Monad m, Monoid a) => WithCommonOptions CSources CxxSources JsSources a -> GenerateFilesWithWarnings m (Section a)
toSect = toSection packageName_ executableNames . first ((mempty <$ globalOptions) <>)

toSections :: (Monad m, Monoid a) => Maybe (Map String (WithCommonOptions CSources CxxSources JsSources a)) -> Warnings m (Map String (Section a))
toSections :: (Monad m, Monoid a) => Maybe (Map String (WithCommonOptions CSources CxxSources JsSources a)) -> GenerateFilesWithWarnings m (Map String (Section a))
toSections = maybe (return mempty) (traverse toSect)

toLib = liftIO . toLibrary dir packageName_
Expand All @@ -1125,12 +1145,12 @@ toPackage_ dir (Product g PackageConfig{..}) = do
++ concatMap sectionSourceDirs benchmarks
)

extraSourceFiles <- expandGlobs "extra-source-files" dir (fromMaybeList packageConfigExtraSourceFiles)
extraDocFiles <- expandGlobs "extra-doc-files" dir (fromMaybeList packageConfigExtraDocFiles)
extraSourceFiles <- liftWarnings $ expandGlobs "extra-source-files" dir (fromMaybeList packageConfigExtraSourceFiles)
extraDocFiles <- liftWarnings $ expandGlobs "extra-doc-files" dir (fromMaybeList packageConfigExtraDocFiles)

let dataBaseDir = maybe dir (dir </>) packageConfigDataDir

dataFiles <- expandGlobs "data-files" dataBaseDir (fromMaybeList packageConfigDataFiles)
dataFiles <- liftWarnings $ expandGlobs "data-files" dataBaseDir (fromMaybeList packageConfigDataFiles)

let
licenseFiles :: [String]
Expand All @@ -1143,7 +1163,7 @@ toPackage_ dir (Product g PackageConfig{..}) = do
input <- liftIO (tryReadFile (dir </> file))
case input >>= inferLicense of
Nothing -> do
tell ["Inferring license from file " ++ file ++ " failed!"]
liftWarnings $ tell ["Inferring license from file " ++ file ++ " failed!"]
return Nothing
license -> return license
_ -> return Nothing
Expand Down Expand Up @@ -1182,8 +1202,8 @@ toPackage_ dir (Product g PackageConfig{..}) = do
, packageVerbatim = fromMaybeList globalVerbatim
}

tell nameWarnings
tell (formatMissingSourceDirs missingSourceDirs)
liftWarnings $ tell nameWarnings
liftWarnings $ tell (formatMissingSourceDirs missingSourceDirs)
return (determineCabalVersion inferredLicense pkg)
where
nameWarnings :: [String]
Expand Down Expand Up @@ -1394,13 +1414,20 @@ expandMain = flatten . expand
, sectionConditionals = map (fmap flatten) sectionConditionals
}

toSection :: Monad m => String -> [String] -> WithCommonOptions CSources CxxSources JsSources a -> Warnings m (Section a)
type GenerateFilesWithWarnings = WriterT [Either GenerateFile String]

liftWarnings :: Functor m => Warnings m a -> GenerateFilesWithWarnings m a
liftWarnings = mapWriterT (fmap (fmap $ map Right))

toSection :: Monad m => String -> [String] -> WithCommonOptions CSources CxxSources JsSources a -> GenerateFilesWithWarnings m (Section a)
toSection packageName_ executableNames = go
where
go :: Monad m => WithCommonOptions CSources CxxSources JsSources a -> GenerateFilesWithWarnings m (Section a)
go (Product CommonOptions{..} a) = do
(systemBuildTools, buildTools) <- maybe (return mempty) toBuildTools commonOptionsBuildTools

conditionals <- mapM toConditional (fromMaybeList commonOptionsWhen)
tell (map Left $ fromMaybeList commonOptionsGenerateFile)
return Section {
sectionData = a
, sectionSourceDirs = nub $ fromMaybeList commonOptionsSourceDirs
Expand Down Expand Up @@ -1430,15 +1457,15 @@ toSection packageName_ executableNames = go
, sectionSystemBuildTools = systemBuildTools <> fromMaybe mempty commonOptionsSystemBuildTools
, sectionVerbatim = fromMaybeList commonOptionsVerbatim
}
toBuildTools :: Monad m => BuildTools -> Warnings m (SystemBuildTools, Map BuildTool DependencyVersion)
toBuildTools = fmap (mkSystemBuildTools &&& mkBuildTools) . mapM (toBuildTool packageName_ executableNames). unBuildTools
toBuildTools :: Monad m => BuildTools -> GenerateFilesWithWarnings m (SystemBuildTools, Map BuildTool DependencyVersion)
toBuildTools = fmap (mkSystemBuildTools &&& mkBuildTools) . mapM (liftWarnings . toBuildTool packageName_ executableNames). unBuildTools
where
mkSystemBuildTools :: [Either (String, VersionConstraint) b] -> SystemBuildTools
mkSystemBuildTools = SystemBuildTools . Map.fromList . lefts

mkBuildTools = Map.fromList . rights

toConditional :: Monad m => ConditionalSection CSources CxxSources JsSources a -> Warnings m (Conditional (Section a))
toConditional :: Monad m => ConditionalSection CSources CxxSources JsSources a -> GenerateFilesWithWarnings m (Conditional (Section a))
toConditional x = case x of
ThenElseConditional (Product (ThenElse then_ else_) c) -> conditional c <$> (go then_) <*> (Just <$> go else_)
FlatConditional (Product sect c) -> conditional c <$> (go sect) <*> pure Nothing
Expand Down
15 changes: 15 additions & 0 deletions src/Hpack/Utf8.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ module Hpack.Utf8 (
encodeUtf8
, readFile
, writeFile
, ensureFile
, putStr
, hPutStr
, hPutStrLn
) where

import Prelude hiding (readFile, writeFile, putStr)

import Imports

import qualified Data.Text as T
import qualified Data.Text.Encoding as Encoding
import Data.Text.Encoding.Error (lenientDecode)
import qualified Data.ByteString as B
import System.IO (Handle, stdout, IOMode(..), withFile, Newline(..), nativeNewline)
import System.Directory
import System.FilePath

encodeUtf8 :: String -> B.ByteString
encodeUtf8 = Encoding.encodeUtf8 . T.pack
Expand Down Expand Up @@ -59,3 +64,13 @@ hPutStrLn h xs = hPutStr h xs >> hPutStr h "\n"

hPutStr :: Handle -> String -> IO ()
hPutStr h = B.hPutStr h . encodeText

ensureFile :: FilePath -> String -> IO ()
ensureFile name new = do
exists <- doesFileExist name
if exists then do
old <- readFile name
unless (old == new) $ writeFile name new
else do
createDirectoryIfMissing True (takeDirectory name)
writeFile name new
Loading