diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b13473e..59f67b32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## Changes in 0.30.0 + - Transparently add `build-tool-depends` on `hspec-discover`/`markdown-unlit` + when a packages depends on `hspec`/`markdown-unlit` for compatibility of + existing package specifications with `cabal new-build` (see #254) + ## Changes in 0.29.5 - Fix a regression related to indentation sniffing (close #310) diff --git a/README.md b/README.md index 7230007d..b0ec7c14 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +TODO: ConCon: A convention over configuration extension for Hpack + # hpack: A modern format for Haskell packages Hpack is a format for Haskell packages. It is a modern alternative to the @@ -35,6 +37,30 @@ at the Singapore Haskell meetup: http://typeful.net/talks/hpack ## Documentation +### Build tools and compatibility with cabal new-build + +When using sandboxes, `cabal` makes all executables of all transitive +dependencies available during the build so that they can be used as build +tools. + +However, with `cabal new-build` build tools have to be specified explicitly via +`build-tool-depends`. + +For compatibility of existing package specifications with `cabal new-build` we +transparently add `build-tool-depends` when a package directly depends on +certain packages. + +- When a package depends on `hspec` then `hpack` adds `hspec-discover` to `build-tool-depends` +- When a package depends on `markdown-unlit` then `hpack` adds `markdown-unlit` to `build-tool-depends` + +You can opt out of this behavior by adding + +```yaml +verbatim: + build-tool-depends: null +``` +to `package.yaml`. + ### Quick-reference #### Top-level fields @@ -149,7 +175,7 @@ this reason it is recommended to only use tags as Git references. | --- | --- | --- | --- | --- | | `dependencies` | `setup-depends` | | Implies `build-type: Custom` | | -#### 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. @@ -178,8 +204,48 @@ values are merged with per section values. | `ld-options` | · | | | | `dependencies` | `build-depends` | | | | `pkg-config-dependencies` | `pkgconfig-depends` | | | -| `build-tools` | · | | | | `when` | | | Accepts a list of conditionals (see [Conditionals](#conditionals)) | +| `haskell-build-tools` | `build-tools` and/or `build-tool-depends` | | | +| `system-build-tools` | `build-tools` | | | -- FIXME: separate PR + +**`build-tools`: A set of Haskell executables that are needed to build this component** + +Each entry consists of a *name* and an optional *version constraint*. + +The name can be specified in two ways: + +1. Qualified: `:` +1. Unqualified: `` + +A qualified name refers to an executable named `` in a +package named ``. + +An unqualified name refers to one of two things: + +1. Another executable from the same package named `` +2. An executable from another package where the executable name and the package name are identical + +The first takes precedence over the second; if this is undesired then +qualified names must be used instead. + +Examples: +```yaml +build-tools: + - alex + - happy:happy + +build-tools: + alex:alex: 0.1.0 + alex: 0.1.0 +``` + + +`build-tools` field subsumes Cabal's `build-tools` and +`build-tool-depends` fields. + +alex + + #### Library fields diff --git a/hpack.cabal b/hpack.cabal index ae31eb54..4ee8203f 100644 --- a/hpack.cabal +++ b/hpack.cabal @@ -1,13 +1,13 @@ cabal-version: >= 1.10 --- This file has been generated from package.yaml by hpack version 0.29.5. +-- This file has been generated from package.yaml by hpack version 0.30.0. -- -- see: https://github.com/sol/hpack -- --- hash: 0ff0fec4e3939cb7b4ceee3340f6b006edeac6c8114cb165b6495e61a76c79c0 +-- hash: d081afc89b25afda429f687327eccfed9c8212f1f01f3ed42966c1706e518152 name: hpack -version: 0.29.5 +version: 0.30.0 synopsis: A modern format for Haskell packages description: See README at category: Development @@ -148,6 +148,8 @@ test-suite spec , unordered-containers , vector , yaml >=0.8.28 + build-tool-depends: + hspec-discover:hspec-discover ==2.* other-modules: Data.Aeson.Config.FromValueSpec Data.Aeson.Config.TypesSpec diff --git a/package.yaml b/package.yaml index b5bfaaec..b0767a23 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: hpack -version: 0.29.5 +version: 0.30.0 synopsis: A modern format for Haskell packages description: See README at maintainer: Simon Hengel diff --git a/src/Hpack/Config.hs b/src/Hpack/Config.hs index 6757da6c..a15efb28 100644 --- a/src/Hpack/Config.hs +++ b/src/Hpack/Config.hs @@ -39,6 +39,8 @@ module Hpack.Config ( , Dependencies(..) , DependencyVersion(..) , SourceDependency(..) +, BuildTools +, BuildTool(..) , GitRef , GitUrl , GhcOption @@ -170,7 +172,7 @@ packageDependencies Package{..} = nub . sortBy (comparing (lexicographically . f deps xs = [(name, version) | (name, version) <- (Map.toList . unDependencies . sectionDependencies) xs] section :: a -> Section a -section a = Section a [] mempty [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] Nothing [] mempty [] +section a = Section a [] mempty [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] [] Nothing [] mempty mempty [] packageConfig :: FilePath packageConfig = "package.yaml" @@ -644,7 +646,7 @@ determineCabalVersion inferredLicense pkg@Package{..} = ( | version >= makeVersion [2,1] = showVersion version | otherwise = (">= " ++) . showVersion $ version - version = fromMaybe (makeVersion [1,10]) $ maximum [ + version = fromMaybe (makeVersion [1,12]) $ maximum [ packageCabalVersion , packageLibrary >>= libraryCabalVersion , internalLibsCabalVersion packageInternalLibraries @@ -788,9 +790,17 @@ data Section a = Section { , sectionBuildable :: Maybe Bool , sectionConditionals :: [Conditional (Section a)] , sectionBuildTools :: Dependencies +, sectionBuildToolDepends :: BuildTools , sectionVerbatim :: [Verbatim] } deriving (Eq, Show, Functor, Foldable, Traversable) +type BuildTools = Map BuildTool DependencyVersion + +data BuildTool = BuildTool { + buildToolPackage :: String +, buildToolExecutable :: String +} deriving (Eq, Ord, Show) + data Conditional a = Conditional { conditionalCondition :: String , conditionalThen :: a @@ -1228,13 +1238,15 @@ toSection_ (Product CommonOptions{..} a) = Section { , sectionInstallIncludes = fromMaybeList commonOptionsInstallIncludes , sectionLdOptions = fromMaybeList commonOptionsLdOptions , sectionBuildable = commonOptionsBuildable - , sectionDependencies = fromMaybe mempty commonOptionsDependencies + , sectionDependencies = dependencies , sectionPkgConfigDependencies = fromMaybeList commonOptionsPkgConfigDependencies , sectionConditionals = conditionals , sectionBuildTools = fromMaybe mempty commonOptionsBuildTools + , sectionBuildToolDepends = hardCodedBuildToolsFromDependencies dependencies , sectionVerbatim = fromMaybeList commonOptionsVerbatim } where + dependencies = fromMaybe mempty commonOptionsDependencies conditionals = map toConditional (fromMaybeList commonOptionsWhen) toConditional :: ConditionalSection CSources CxxSources JsSources a -> Conditional (Section a) @@ -1244,6 +1256,15 @@ toSection_ (Product CommonOptions{..} a) = Section { where conditional (Condition (Cond c)) = Conditional c +hardCodedBuildToolsFromDependencies :: Dependencies -> BuildTools +hardCodedBuildToolsFromDependencies = Map.fromList . mapMaybe f . Map.toList . unDependencies + where + f (pkg, version) = (,) <$> lookup pkg known <*> pure version + known = [ + ("hspec", BuildTool "hspec-discover" "hspec-discover") + , ("markdown-unlit", BuildTool "markdown-unlit" "markdown-unlit") + ] + pathsModuleFromPackageName :: String -> String pathsModuleFromPackageName name = "Paths_" ++ map f name where diff --git a/src/Hpack/Render.hs b/src/Hpack/Render.hs index 9fd6ab3e..1575fbb3 100644 --- a/src/Hpack/Render.hs +++ b/src/Hpack/Render.hs @@ -242,6 +242,7 @@ renderSection renderSectionData extraFieldsStart extraFieldsEnd Section{..} = ad , renderDependencies "build-depends" sectionDependencies , Field "pkgconfig-depends" (CommaSeparatedList sectionPkgConfigDependencies) , renderDependencies "build-tools" sectionBuildTools + , renderBuildToolDepends sectionBuildToolDepends ] ++ maybe [] (return . renderBuildable) sectionBuildable ++ map (renderConditional renderSectionData) sectionConditionals @@ -312,13 +313,20 @@ renderSignatures = Field "signatures" . CommaSeparatedList renderDependencies :: String -> Dependencies -> Element renderDependencies name = Field name . CommaSeparatedList . map renderDependency . Map.toList . unDependencies +renderBuildToolDepends :: BuildTools -> Element +renderBuildToolDepends = Field "build-tool-depends" . CommaSeparatedList . map renderBuildTool . Map.toList + +renderBuildTool :: (BuildTool, DependencyVersion) -> String +renderBuildTool (BuildTool pkg executable, version) = pkg ++ ":" ++ executable ++ renderVersion version + renderDependency :: (String, DependencyVersion) -> String -renderDependency (name, version) = name ++ v - where - v = case version of - AnyVersion -> "" - VersionRange x -> " " ++ x - SourceDependency _ -> "" +renderDependency (name, version) = name ++ renderVersion version + +renderVersion :: DependencyVersion -> String +renderVersion version = case version of + AnyVersion -> "" + VersionRange x -> " " ++ x + SourceDependency _ -> "" renderGhcOptions :: [GhcOption] -> Element renderGhcOptions = Field "ghc-options" . WordList diff --git a/test/EndToEndSpec.hs b/test/EndToEndSpec.hs index 86bf448e..828b08ab 100644 --- a/test/EndToEndSpec.hs +++ b/test/EndToEndSpec.hs @@ -435,6 +435,17 @@ spec = around_ (inTempDirectoryNamed "foo") $ do - "*.markdown" |] `shouldWarn` ["Specified pattern \"*.markdown\" for extra-doc-files does not match any files"] + describe "build-tools" $ do + it "accepts known build tool" $ do + [i| + executable: + build-tools: + alex == 0.1.0 + |] `shouldRenderTo` executable_ "foo" [i| + build-tools: + alex ==0.1.0 + |] + describe "dependencies" $ do it "accepts single dependency" $ do [i| @@ -457,17 +468,42 @@ spec = around_ (inTempDirectoryNamed "foo") $ do , transformers |] + context "when package depends on hspec" $ do + it "adds build-tool-depends on hspec-discover:hspec-discover" $ do + [i| + executable: + dependencies: + - hspec == 2.* + |] `shouldRenderTo` executable_ "foo" [i| + build-depends: + hspec ==2.* + build-tool-depends: + hspec-discover:hspec-discover ==2.* + |] + + it "gives verbatim precedence" $ do + [i| + executable: + dependencies: + - hspec == 2.* + verbatim: + build-tool-depends: null + |] `shouldRenderTo` executable_ "foo" [i| + build-depends: + hspec ==2.* + |] + context "with both global and section specific dependencies" $ do it "combines dependencies" $ do [i| dependencies: - base executable: - dependencies: hspec + dependencies: transformers |] `shouldRenderTo` executable_ "foo" [i| build-depends: base - , hspec + , transformers |] it "gives section specific dependencies precedence" $ do @@ -1413,7 +1449,7 @@ executable #{name} |] package :: String -> Package -package = Package "foo" "0.0.0" "Simple" ">= 1.10" +package = Package "foo" "0.0.0" "Simple" ">= 1.12" data Package = Package { packageName :: String