Skip to content

Commit 93248ac

Browse files
committed
Add generate-file
1 parent d2fa993 commit 93248ac

File tree

9 files changed

+137
-42
lines changed

9 files changed

+137
-42
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## Changes in 0.34.5
2+
- Add support for `generate-file`
3+
14
## Changes in 0.34.4
25
- Render `default-extensions` / `other-extensions` line-separated
36
- Compatibility with `Cabal-3.4.0.0`

hpack.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ cabal-version: 1.12
55
-- see: https://github.com/sol/hpack
66

77
name: hpack
8-
version: 0.34.4
8+
version: 0.35.0
99
synopsis: A modern format for Haskell packages
1010
description: See README at <https://github.com/sol/hpack#readme>
1111
category: Development

package.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: hpack
2-
version: 0.34.4
2+
version: 0.35.0
33
synopsis: A modern format for Haskell packages
44
description: See README at <https://github.com/sol/hpack#readme>
55
maintainer: Simon Hengel <[email protected]>

src/Hpack.hs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ hpackResult = hpackResultWithVersion version
192192

193193
hpackResultWithVersion :: Version -> Options -> IO Result
194194
hpackResultWithVersion v (Options options force generateHashStrategy toStdout) = do
195-
DecodeResult pkg (lines -> cabalVersion) cabalFileName warnings <- readPackageConfig options >>= either die return
195+
DecodeResult pkg (lines -> cabalVersion) cabalFileName files warnings <- readPackageConfig options >>= either die return
196196
mExistingCabalFile <- readCabalFile cabalFileName
197197
let
198198
newCabalFile = makeCabalFile generateHashStrategy mExistingCabalFile cabalVersion v pkg
@@ -205,6 +205,13 @@ hpackResultWithVersion v (Options options force generateHashStrategy toStdout) =
205205
Generated -> writeCabalFile options toStdout cabalFileName newCabalFile
206206
_ -> return ()
207207

208+
let generateFiles = mapM_ (uncurry ensureFile) files
209+
case status of
210+
Generated -> generateFiles
211+
OutputUnchanged -> generateFiles
212+
AlreadyGeneratedByNewerHpack -> return ()
213+
ExistingCabalFileWasModifiedManually -> return ()
214+
208215
return Result {
209216
resultWarnings = warnings
210217
, resultCabalFile = cabalFileName

src/Hpack/Config.hs

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,14 @@ data CommonOptions cSources cxxSources jsSources a = CommonOptions {
292292
, commonOptionsBuildTools :: Maybe BuildTools
293293
, commonOptionsSystemBuildTools :: Maybe SystemBuildTools
294294
, commonOptionsVerbatim :: Maybe (List Verbatim)
295+
, commonOptionsGenerateFile :: Maybe (List GenerateFile)
295296
} deriving (Functor, Generic)
296297

298+
data GenerateFile = GenerateFile {
299+
generateFileName :: FilePath
300+
, generateFileContents :: String
301+
} deriving (Generic, FromValue)
302+
297303
type ParseCommonOptions = CommonOptions ParseCSources ParseCxxSources ParseJsSources
298304
instance FromValue a => FromValue (ParseCommonOptions a)
299305

@@ -325,6 +331,7 @@ instance (Semigroup cSources, Semigroup cxxSources, Semigroup jsSources, Monoid
325331
, commonOptionsBuildTools = Nothing
326332
, commonOptionsSystemBuildTools = Nothing
327333
, commonOptionsVerbatim = Nothing
334+
, commonOptionsGenerateFile = Nothing
328335
}
329336
mappend = (<>)
330337

@@ -356,6 +363,7 @@ instance (Semigroup cSources, Semigroup cxxSources, Semigroup jsSources) => Semi
356363
, commonOptionsBuildTools = commonOptionsBuildTools a <> commonOptionsBuildTools b
357364
, commonOptionsSystemBuildTools = commonOptionsSystemBuildTools b <> commonOptionsSystemBuildTools a
358365
, commonOptionsVerbatim = commonOptionsVerbatim a <> commonOptionsVerbatim b
366+
, commonOptionsGenerateFile = commonOptionsGenerateFile a <> commonOptionsGenerateFile b
359367
}
360368

361369
type ParseCSources = Maybe (List FilePath)
@@ -644,6 +652,7 @@ data DecodeResult = DecodeResult {
644652
decodeResultPackage :: Package
645653
, decodeResultCabalVersion :: String
646654
, decodeResultCabalFile :: FilePath
655+
, decodeResultGenerateFiles :: [(FilePath, String)]
647656
, decodeResultWarnings :: [String]
648657
} deriving (Eq, Show)
649658

@@ -656,8 +665,16 @@ readPackageConfig (DecodeOptions programName file mUserDataDir readValue) = runE
656665
userDataDir <- liftIO $ maybe (getAppUserDataDirectory "hpack") return mUserDataDir
657666
toPackage programName userDataDir dir config
658667
where
659-
addCabalFile :: ((Package, String), [String]) -> DecodeResult
660-
addCabalFile ((pkg, cabalVersion), warnings) = DecodeResult pkg cabalVersion (takeDirectory_ file </> (packageName pkg ++ ".cabal")) warnings
668+
addCabalFile :: ((Package, String, [GenerateFile]), [String]) -> DecodeResult
669+
addCabalFile ((pkg, cabalVersion, generateFiles), warnings) = DecodeResult {
670+
decodeResultPackage = pkg
671+
, decodeResultCabalVersion = cabalVersion
672+
, decodeResultCabalFile = addPackageDir (packageName pkg ++ ".cabal")
673+
, decodeResultGenerateFiles = map (first addPackageDir . (generateFileName &&& generateFileContents)) $ nubOn generateFileName $ reverse generateFiles
674+
, decodeResultWarnings = warnings
675+
}
676+
677+
addPackageDir = (takeDirectory_ file </>)
661678

662679
takeDirectory_ :: FilePath -> FilePath
663680
takeDirectory_ p
@@ -997,11 +1014,14 @@ type ConfigWithDefaults = Product
9971014
type CommonOptionsWithDefaults a = Product DefaultsConfig (CommonOptions ParseCSources ParseCxxSources ParseJsSources a)
9981015
type WithCommonOptionsWithDefaults a = Product DefaultsConfig (WithCommonOptions ParseCSources ParseCxxSources ParseJsSources a)
9991016

1000-
toPackage :: ProgramName -> FilePath -> FilePath -> ConfigWithDefaults -> Warnings (Errors IO) (Package, String)
1017+
toPackage :: ProgramName -> FilePath -> FilePath -> ConfigWithDefaults -> Warnings (Errors IO) (Package, String, [GenerateFile])
10011018
toPackage programName userDataDir dir =
10021019
expandDefaultsInConfig programName userDataDir dir
10031020
>=> traverseConfig (expandForeignSources dir)
1004-
>=> toPackage_ dir
1021+
>=> runGenerateFilesWithWarnings . toPackage_ dir
1022+
1023+
runGenerateFilesWithWarnings :: Functor m => GenerateFilesWithWarnings m (a, b) -> Warnings m (a, b, [GenerateFile])
1024+
runGenerateFilesWithWarnings = mapWriterT (fmap $ \ ((a, b), c) -> ((a, b, lefts c), rights c))
10051025

10061026
expandDefaultsInConfig
10071027
:: ProgramName
@@ -1090,19 +1110,19 @@ toExecutableMap name executables mExecutable = do
10901110

10911111
type GlobalOptions = CommonOptions CSources CxxSources JsSources Empty
10921112

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

11001120
executableNames = maybe [] Map.keys executableMap
11011121

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

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

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

1128-
extraSourceFiles <- expandGlobs "extra-source-files" dir (fromMaybeList packageConfigExtraSourceFiles)
1129-
extraDocFiles <- expandGlobs "extra-doc-files" dir (fromMaybeList packageConfigExtraDocFiles)
1148+
extraSourceFiles <- liftWarnings $ expandGlobs "extra-source-files" dir (fromMaybeList packageConfigExtraSourceFiles)
1149+
extraDocFiles <- liftWarnings $ expandGlobs "extra-doc-files" dir (fromMaybeList packageConfigExtraDocFiles)
11301150

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

1133-
dataFiles <- expandGlobs "data-files" dataBaseDir (fromMaybeList packageConfigDataFiles)
1153+
dataFiles <- liftWarnings $ expandGlobs "data-files" dataBaseDir (fromMaybeList packageConfigDataFiles)
11341154

11351155
let
11361156
licenseFiles :: [String]
@@ -1143,7 +1163,7 @@ toPackage_ dir (Product g PackageConfig{..}) = do
11431163
input <- liftIO (tryReadFile (dir </> file))
11441164
case input >>= inferLicense of
11451165
Nothing -> do
1146-
tell ["Inferring license from file " ++ file ++ " failed!"]
1166+
liftWarnings $ tell ["Inferring license from file " ++ file ++ " failed!"]
11471167
return Nothing
11481168
license -> return license
11491169
_ -> return Nothing
@@ -1182,8 +1202,8 @@ toPackage_ dir (Product g PackageConfig{..}) = do
11821202
, packageVerbatim = fromMaybeList globalVerbatim
11831203
}
11841204

1185-
tell nameWarnings
1186-
tell (formatMissingSourceDirs missingSourceDirs)
1205+
liftWarnings $ tell nameWarnings
1206+
liftWarnings $ tell (formatMissingSourceDirs missingSourceDirs)
11871207
return (determineCabalVersion inferredLicense pkg)
11881208
where
11891209
nameWarnings :: [String]
@@ -1394,13 +1414,20 @@ expandMain = flatten . expand
13941414
, sectionConditionals = map (fmap flatten) sectionConditionals
13951415
}
13961416

1397-
toSection :: Monad m => String -> [String] -> WithCommonOptions CSources CxxSources JsSources a -> Warnings m (Section a)
1417+
type GenerateFilesWithWarnings = WriterT [Either GenerateFile String]
1418+
1419+
liftWarnings :: Functor m => Warnings m a -> GenerateFilesWithWarnings m a
1420+
liftWarnings = mapWriterT (fmap (fmap $ map Right))
1421+
1422+
toSection :: Monad m => String -> [String] -> WithCommonOptions CSources CxxSources JsSources a -> GenerateFilesWithWarnings m (Section a)
13981423
toSection packageName_ executableNames = go
13991424
where
1425+
go :: Monad m => WithCommonOptions CSources CxxSources JsSources a -> GenerateFilesWithWarnings m (Section a)
14001426
go (Product CommonOptions{..} a) = do
14011427
(systemBuildTools, buildTools) <- maybe (return mempty) toBuildTools commonOptionsBuildTools
14021428

14031429
conditionals <- mapM toConditional (fromMaybeList commonOptionsWhen)
1430+
tell (map Left $ fromMaybeList commonOptionsGenerateFile)
14041431
return Section {
14051432
sectionData = a
14061433
, sectionSourceDirs = nub $ fromMaybeList commonOptionsSourceDirs
@@ -1430,15 +1457,15 @@ toSection packageName_ executableNames = go
14301457
, sectionSystemBuildTools = systemBuildTools <> fromMaybe mempty commonOptionsSystemBuildTools
14311458
, sectionVerbatim = fromMaybeList commonOptionsVerbatim
14321459
}
1433-
toBuildTools :: Monad m => BuildTools -> Warnings m (SystemBuildTools, Map BuildTool DependencyVersion)
1434-
toBuildTools = fmap (mkSystemBuildTools &&& mkBuildTools) . mapM (toBuildTool packageName_ executableNames). unBuildTools
1460+
toBuildTools :: Monad m => BuildTools -> GenerateFilesWithWarnings m (SystemBuildTools, Map BuildTool DependencyVersion)
1461+
toBuildTools = fmap (mkSystemBuildTools &&& mkBuildTools) . mapM (liftWarnings . toBuildTool packageName_ executableNames). unBuildTools
14351462
where
14361463
mkSystemBuildTools :: [Either (String, VersionConstraint) b] -> SystemBuildTools
14371464
mkSystemBuildTools = SystemBuildTools . Map.fromList . lefts
14381465

14391466
mkBuildTools = Map.fromList . rights
14401467

1441-
toConditional :: Monad m => ConditionalSection CSources CxxSources JsSources a -> Warnings m (Conditional (Section a))
1468+
toConditional :: Monad m => ConditionalSection CSources CxxSources JsSources a -> GenerateFilesWithWarnings m (Conditional (Section a))
14421469
toConditional x = case x of
14431470
ThenElseConditional (Product (ThenElse then_ else_) c) -> conditional c <$> (go then_) <*> (Just <$> go else_)
14441471
FlatConditional (Product sect c) -> conditional c <$> (go sect) <*> pure Nothing

src/Hpack/Utf8.hs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@ module Hpack.Utf8 (
22
encodeUtf8
33
, readFile
44
, writeFile
5+
, ensureFile
56
, putStr
67
, hPutStr
78
, hPutStrLn
89
) where
910

1011
import Prelude hiding (readFile, writeFile, putStr)
1112

13+
import Imports
14+
1215
import qualified Data.Text as T
1316
import qualified Data.Text.Encoding as Encoding
1417
import Data.Text.Encoding.Error (lenientDecode)
1518
import qualified Data.ByteString as B
1619
import System.IO (Handle, stdout, IOMode(..), withFile, Newline(..), nativeNewline)
20+
import System.Directory
21+
import System.FilePath
1722

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

6065
hPutStr :: Handle -> String -> IO ()
6166
hPutStr h = B.hPutStr h . encodeText
67+
68+
ensureFile :: FilePath -> String -> IO ()
69+
ensureFile name new = do
70+
exists <- doesFileExist name
71+
if exists then do
72+
old <- readFile name
73+
unless (old == new) $ writeFile name new
74+
else do
75+
createDirectoryIfMissing True (takeDirectory name)
76+
writeFile name new

test/EndToEndSpec.hs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,28 @@ spec = around_ (inTempDirectoryNamed "foo") $ do
160160
data/foo/index.html
161161
|]
162162

163+
describe "generate-file" $ do
164+
it "generates files" $ do
165+
[i|
166+
generate-file:
167+
name: Setup.hs
168+
contents: |
169+
import Distribution.Simple
170+
main = defaultMain
171+
library: {}
172+
|] `shouldGenerateFiles` [("Setup.hs", "import Distribution.Simple\nmain = defaultMain\n")]
173+
174+
it "gives later occurrences precedence" $ do
175+
[i|
176+
generate-file:
177+
name: foo
178+
contents: bar
179+
library:
180+
generate-file:
181+
name: foo
182+
contents: baz
183+
|] `shouldGenerateFiles` [("foo", "baz")]
184+
163185
describe "data-dir" $ do
164186
it "accepts data-dir" $ do
165187
touch "data/foo.html"
@@ -1672,21 +1694,21 @@ spec = around_ (inTempDirectoryNamed "foo") $ do
16721694
author: John Doe
16731695
|]
16741696

1675-
run :: HasCallStack => FilePath -> FilePath -> String -> IO ([String], String)
1697+
run :: HasCallStack => FilePath -> FilePath -> String -> IO ([String], String, [(FilePath, String)])
16761698
run userDataDir c old = run_ userDataDir c old >>= either assertFailure return
16771699

1678-
run_ :: FilePath -> FilePath -> String -> IO (Either String ([String], String))
1700+
run_ :: FilePath -> FilePath -> String -> IO (Either String ([String], String, [(FilePath, String)]))
16791701
run_ userDataDir c old = do
16801702
mPackage <- readPackageConfig defaultDecodeOptions {decodeOptionsTarget = c, decodeOptionsUserDataDir = Just userDataDir}
16811703
return $ case mPackage of
1682-
Right (DecodeResult pkg cabalVersion _ warnings) ->
1704+
Right (DecodeResult pkg cabalVersion _ generateFiles warnings) ->
16831705
let
16841706
FormattingHints{..} = sniffFormattingHints (lines old)
16851707
alignment = fromMaybe 0 formattingHintsAlignment
16861708
settings = formattingHintsRenderSettings
16871709
output = cabalVersion ++ Hpack.renderPackageWith settings alignment formattingHintsFieldOrder formattingHintsSectionsFieldOrder pkg
16881710
in
1689-
Right (warnings, output)
1711+
Right (warnings, output, generateFiles)
16901712
Left err -> Left err
16911713

16921714
data RenderResult = RenderResult [String] String
@@ -1701,16 +1723,22 @@ shouldRenderTo input p = do
17011723
let currentDirectory = ".working-directory"
17021724
createDirectory currentDirectory
17031725
withCurrentDirectory currentDirectory $ do
1704-
(warnings, output) <- run ".." (".." </> packageConfig) expected
1726+
(warnings, output, _) <- run ".." (".." </> packageConfig) expected
17051727
RenderResult warnings (dropEmptyLines output) `shouldBe` RenderResult (packageWarnings p) expected
17061728
where
17071729
expected = dropEmptyLines (renderPackage p)
17081730
dropEmptyLines = unlines . filter (not . null) . lines
17091731

1732+
shouldGenerateFiles :: HasCallStack => String -> [(FilePath, String)] -> Expectation
1733+
shouldGenerateFiles input files = do
1734+
writeFile packageConfig input
1735+
(_, _, generateFiles) <- run "" packageConfig ""
1736+
generateFiles `shouldBe` files
1737+
17101738
shouldWarn :: HasCallStack => String -> [String] -> Expectation
17111739
shouldWarn input expected = do
17121740
writeFile packageConfig input
1713-
(warnings, _) <- run "" packageConfig ""
1741+
(warnings, _, _) <- run "" packageConfig ""
17141742
sort warnings `shouldBe` sort expected
17151743

17161744
shouldFailWith :: HasCallStack => String -> String -> Expectation

test/Hpack/ConfigSpec.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ withPackage content beforeAction expectation = withTempDirectory $ \dir_ -> do
6565
writeFile (dir </> "package.yaml") content
6666
withCurrentDirectory dir beforeAction
6767
r <- readPackageConfig (testDecodeOptions $ dir </> "package.yaml")
68-
either expectationFailure (\ (DecodeResult p _ _ warnings) -> expectation (p, warnings)) r
68+
either expectationFailure (\ (DecodeResult p _ _ _ warnings) -> expectation (p, warnings)) r
6969

7070
withPackageConfig :: String -> IO () -> (Package -> Expectation) -> Expectation
7171
withPackageConfig content beforeAction expectation = withPackage content beforeAction (expectation . fst)

0 commit comments

Comments
 (0)