packages feed

cabal-build-programs (empty) → 0.1.0.0

raw patch · 5 files changed

+456/−0 lines, 5 filesdep +Cabaldep +basesetup-changed

Dependencies added: Cabal, base

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Aditya Siram (c) 2020++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Aditya Siram nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.org view
@@ -0,0 +1,9 @@+* cabal-build-programs+  A Cabal library that allows build time dependencies on non-Haskell+  executables. As of this writing there is no convenient Cabal field for+  non-Haskell programs that are sometimes useful in a polyglot build like+  'cmake' or 'gprof'. This library solves that by providing a custom Cabal field+  "x-build-programs" which contains a list of such dependencies.++  See the [[https://hackage.haskell.org/package/cabal-build-programs/docs/Distribution-Simple-BuildPrograms.html][Haddock documentation]] for much more detail.+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cabal-build-programs.cabal view
@@ -0,0 +1,39 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.2.+--+-- see: https://github.com/sol/hpack+--+-- hash: fc55553ca764af941d36f1d2ab303878f4af5e82f36b24cf7d98008ec0dbbc4e++name:           cabal-build-programs+version:        0.1.0.0+synopsis:       Adds executable dependencies to the Cabal build+description:    Please see the README on GitHub at <https://github.com/deech/cabal-build-programs#readme>+category:       Distribution+homepage:       https://github.com/deech/cabal-build-programs#readme+bug-reports:    https://github.com/deech/cabal-build-programs/issues+author:         Aditya Siram+maintainer:     aditya.siram@gmail.com+copyright:      Aditya Siram, 2019+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.org++source-repository head+  type: git+  location: https://github.com/deech/cabal-build-programs++library+  exposed-modules:+      Distribution.Simple.BuildPrograms+  other-modules:+      Paths_cabal_build_programs+  hs-source-dirs:+      src+  build-depends:+      Cabal >=2.2.0.0 && <4+    , base >=4.7 && <5+  default-language: Haskell2010
+ src/Distribution/Simple/BuildPrograms.hs view
@@ -0,0 +1,376 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Distribution.Simple.BuildPrograms+    ( -- * Introduction+      --+      -- $introduction++      -- * Why?+      --+      -- $why++      -- * Getting Started+      --+      -- $gettingstarted++      -- * Limitations+      --+      -- $limitations++      -- * Stack & HPack Gotchas+      --+      -- $stackHPackGotchas++      buildProgramsCustomField+    , defaultMain+    , buildProgramsUserHooks+    , localBuildInfoWithBuildPrograms+    , componentBuildPrograms+    ) where++import Distribution.Simple(UserHooks(..),simpleUserHooks, defaultMainWithHooks)+import Distribution.Simple.Setup(BuildFlags(..), fromFlag,ReplFlags(..))+import Distribution.Types.LocalBuildInfo(LocalBuildInfo(..), neededTargetsInBuildOrder')+import Distribution.Types.PackageDescription(PackageDescription(..))+import Distribution.Types.Component(Component(..), componentBuildInfo, componentName)+import Distribution.Compat.Graph(nodeKey)+import Distribution.Types.BuildInfo(BuildInfo(customFieldsBI))+import Distribution.Types.TargetInfo(TargetInfo(..))+import Distribution.Simple.UserHooks(Args)+import Distribution.Simple.BuildTarget(readTargetInfos)+import Data.List(concatMap,unfoldr,nub,intersperse)+import Distribution.Verbosity(Verbosity)+import Control.Exception(catch,IOException)+import Control.Monad(foldM)+import Distribution.Pretty(prettyShow)+import Distribution.Simple.Program.Db(ProgramDb,requireProgram)+import Distribution.Simple.Program.Types(simpleProgram, Program(programName))+import Distribution.Simple.Utils(die')++{-|+The custom field to use in Cabal stanza to specify external programs.+In a ~.cabal~ file use:++@+x-build-programs: p1,p2,p3+@++whereas in the ~package.yaml~ of a <https://docs.haskellstack.org/en/stable/README/ Stack> or <https://hackage.haskell.org/package/hpack hpack> project you must use+(note that "p1,p2,p3" are quoted):++@+verbatim:+  x-build-programs: "p1,p2,p3"+@+-}+buildProgramsCustomField :: String+buildProgramsCustomField = "x-build-programs"++{-|+This function is the easiest way to use this library in your @Setup.hs@.++The default @Setup.hs@:++@+import Distribution.Simple+main = defaultMain+@++becomes:++@+import Distribution.Simple.BuildPrograms -- <- only change+main = defaultMain+@++-}+defaultMain :: IO ()+defaultMain = defaultMainWithHooks buildProgramsUserHooks++{-|+Directly access the 'UserHooks' that back 'defaultMain' when you need finer grained control, they are backed by 'simpleUserHooks':++@+import Distribution.Simple.BuildPrograms+import Distribution.Simple(defaultMainWithHooks)+main = defaultMainWithHooks buildProgramUserHooks+@+-}+buildProgramsUserHooks :: UserHooks+buildProgramsUserHooks = overlayUserHooks simpleUserHooks++{-|+If your @Setup.hs@ uses non-default or custom 'UserHooks' ,eg. 'autoconfUserHooks' this function will augment the 'buildHook' and 'replHook' to+first check for the external programs.++@+import Distribution.Simple.BuildPrograms+import Distribution.Simple(defaultMainWithHooks,autoconfUserHooks)+main = defaultMainWithHooks (overlayUserHooks autoconfUserHooks)+@+-}+overlayUserHooks :: UserHooks -> UserHooks+overlayUserHooks hooksToOverlay =+  let bh :: PackageDescription -> LocalBuildInfo -> UserHooks -> BuildFlags -> IO ()+      bh pd lbi uhs bfs = do+        newLbi <- localBuildInfoWithBuildPrograms (buildArgs bfs) (fromFlag (buildVerbosity bfs)) (hookedPrograms uhs) pd lbi+        case newLbi of+          Left err -> die' (fromFlag (buildVerbosity bfs)) err+          Right newLbi' -> (buildHook hooksToOverlay) pd newLbi' uhs bfs+      rh ::  PackageDescription -> LocalBuildInfo -> UserHooks -> ReplFlags -> [String] -> IO ()+      rh pd lbi uhs rfs args = do+        newLbi <- localBuildInfoWithBuildPrograms args (fromFlag (replVerbosity rfs)) (hookedPrograms uhs) pd lbi+        case newLbi of+          Left err -> die' (fromFlag (replVerbosity rfs)) err+          Right newLbi' -> (replHook hooksToOverlay) pd newLbi' uhs rfs args+  in hooksToOverlay { buildHook = bh , replHook = rh }++splitProgramField :: String -> [String]+splitProgramField =+  let isComma t = t == ','+      isCommaOrSpace t = t == ' ' || isComma t+      split s =+        if null s+        then Nothing+        else Just (break isComma (dropWhile isCommaOrSpace s))+  in unfoldr split++programs :: [(String,String)] -> [String]+programs = nub . concatMap (splitProgramField . snd) . filter (\(field,_) -> field == buildProgramsCustomField)++customFields :: TargetInfo -> [(String,String)]+customFields = customFieldsBI . componentBuildInfo . targetComponent++{-|+Used internally it figures out which components to build, extracts the required+external programs into a lookup table. To make debugging easier I made it public+so you print the table.+-}+componentBuildPrograms :: Verbosity -> PackageDescription -> LocalBuildInfo -> Args -> IO [(Component, [String])]+componentBuildPrograms verb pd lbi args =+  let programFields :: TargetInfo -> (Component,[String])+      programFields t = (targetComponent t, programs (customFields t))+  in (map programFields . neededTargetsInBuildOrder' pd lbi . map nodeKey) <$> readTargetInfos verb pd lbi args++{-|+This is the lowest level function provided by this library for use in a highly+customized @Setup.hs@ scripts. Most of the other functions wrap it in some way.++'LocalBuildInfo' is a large datastructure that holds all the information+required to build a project, this function gathers up only the components that+need to be built ( or loaded in GHCi ), extracts the programs in the+"x-build-programs" fields, checks that they exist and adds them as+'ConfiguredProgram' to the 'ProgramDb' of the provided 'LocalBuildInfo' and+returns a new 'LocalBuildInfo' with the new 'ProgramDb'.++If any of the programs are cannot be found a formatted error message is returned.++The expectation is that it is called from a 'buildHook' or a 'replHook' at some+point before the components are built.+-}+localBuildInfoWithBuildPrograms  ::+  Args -- ^ 'buildHook' ( extracted from 'BuildFlags') or 'replHook' arguments+  -> Verbosity+  -> [Program] -- ^ The 'hookedPrograms' from 'UserHooks', used to make sure we don't double check built-in build programs like 'c2hs' or 'ghc'+  -> PackageDescription+  -> LocalBuildInfo+  -> IO (Either String LocalBuildInfo) -- ^ Local build info with the external build programs configured or an error message+localBuildInfoWithBuildPrograms args verbosity hookedPs pd lbi =+  let pdFields = programs (customFieldsPD pd)+      combineAll :: [(Component, [String])] -> [(Maybe Component,[String])]+      combineAll cs = [(Nothing,nub pdFields)] ++ map (\(c,ps) -> (Just c,ps)) cs+      addOrLog :: [String] -> ProgramDb -> [String] -> IO ([String],ProgramDb)+      addOrLog errsSoFar programDb =+        foldM (\(errPs,db) p ->+                 let logError = pure (errPs ++ [p], db)+                 in+                 if p `elem` errsSoFar+                 then logError+                 else+                   if p `elem` (map programName hookedPs)+                   then pure (errPs,db)+                   else+                     catch+                       (do+                         (_,newDb) <- requireProgram verbosity (simpleProgram p) db+                         pure (errPs, newDb))+                       (\(_ :: IOException) -> logError))+              ([],programDb)+      check :: [(Maybe Component,[String])] -> IO ([(Maybe Component, [String])],ProgramDb)+      check =+        foldM (\(compErrs,db) (comp,ps) -> do+                  (errs,newDb) <- addOrLog (concatMap snd compErrs) db ps+                  pure (compErrs ++ [(comp,errs)], newDb))+              ([],withPrograms lbi)+      compLabel :: Maybe Component -> String+      compLabel Nothing = "global"+      compLabel (Just c) = prettyShow (componentName c)+      commaSep :: [String] -> String+      commaSep = concat . intersperse ","+      printErr :: (Maybe Component, [String]) -> String+      printErr (comp,missing) = "- " ++ compLabel comp ++ ": " ++ commaSep missing+  in do+    allPrograms <- combineAll <$> componentBuildPrograms verbosity pd lbi args+    (compErrs, newDb) <- check allPrograms+    let errs = filter (not . null . snd) compErrs+    pure $+      if (not (null errs))+      then Left $ "\n" ++ (unlines ("These build programs are required but could not be found:"+                          : ("- " ++ (commaSep . nub . concatMap snd) errs)+                          : "Broken down by build component: "+                          : map printErr errs))+      else Right $ lbi { withPrograms = newDb }++-- $introduction+-- This is a Cabal library that lets you annotate your+-- @.cabal@ (or @package.yaml@) file with external programs that must be+-- available at build time. It does this using a custom Cabal field,+-- @x-build-programs@ which takes a list of executables that are checked+-- whenever the project is built or when starting GHCi (unfortunately the+-- latter does not work with Stack, see "Stack & HPack Gotchas" below). It can+-- also be specified per build component (library, executables, benchmarks, test+-- suite etc.) so that, for instance, a library does not have to depend on an+-- external program used just for benchmarking (eg. 'gprof').++-- $why+-- This library exists because currently there is no field+-- in Cabal that checks for the existence of non-Haskell programs (eg. '7z.exe'+-- or 'cmake') at build time. The closest ,+-- <https://www.haskell.org/cabal/users-guide/developing-packages.html#pkg-field-build-tool-depends 'build-tool-depends'>+-- only allows Haskell-buildable exectuables like 'c2hs'.++-- $gettingstarted+-- First off you will need to set up a custom build in your @.cabal@:+--+-- @+-- build-type:     Custom+-- custom-setup+--   setup-depends:+--       Cabal >=2.2.0.0 && <4+--     , base >=4.7 && <5+--     , cabal-build-programs+-- @+--+-- or @package.yaml@ file:+--+-- @+-- build-type:          Custom+-- custom-setup:+--   dependencies:+--   - base >= 4.7 && < 5+--   - Cabal >= 2.2.0.0 && < 4+--   - cabal-build-programs+-- @+--+--+-- Then add the external program dependencies, as in this example @package.yaml@:+--+-- @+-- verbatim:+--   x-build-programs: "cmake"+--+-- library:+--   ...+--   verbatim:+--     x-build-programs: "llmv-config"+--   when:+--     - condition: os(windows)+--       then:+--         verbatim:+--           x-build-programs: "7z.exe"+--       else:+--         verbatim:+--           x-build-programs: "zip"+--+-- benchmarks:+--   my-benchmarks:+--     ...+--     verbatim:+--       x-build-programs: "gprof,valgrind"+-- @+--+--+-- A project that uses the snippet above always depends on @cmake@ no matter+-- which artifact is being built but only the library needs @llvm-config@. On+-- Windows the library needs @7z.exe@ for extracting archives, otherwise @zip@+-- and the benchmarks always need the GNU profiling @gprof@ tool and @valgrind@.+--+-- Now we need a custom @Setup.hs@ that can use this library to check those+-- dependencies.+--+-- The easiest way is to use the provided 'defaultMain'. For more advanced use+-- check the docs for 'buildProgramsUserHooks' and+-- 'localBuildInfoWithBuildPrograms'.+--+--++-- $stackHPackGotchas+-- <https://docs.haskellstack.org/en/stable/README/ Stack> and <https://github.com/sol/hpack Hpack> have+-- are a few gotchas to keep in mind and since Stack uses HPack it inherits all the issues as well.+--+--    * Stack seems to completely ignore the 'preRepl' and 'replHook' so+--      when using Stack external programs are not checked when using @stack+--      ghci@ or @stack repl@, @stack build@ seems to work fine.+--+--   * HPack requires that all custom fields be in a "verbatim:" block+--     so the first snippet below is rejected but the second is not:+--+-- @+-- name:                my-awesome-program+-- ...+-- x-build-programs:    "cmake,gprof,someOtherDep"+-- ...+-- @+--+-- @+-- name:                my-awesome-program+-- ...+-- verbatim:+--   x-build-programs:  "cmake,gprof,someOtherDep"+-- ...+-- @+--+--   * Unlike Cabal, HPack will not accumulate across duplicate custom fields+--     and picks the last one it encounters but only at the same level. For+--     example in the first snippet (a) will be ignored by HPack and you will+--     only see (b) in your @.cabal@ file. But in the second snippet since (a)+--     is inside a conditional it does make it in. To be fair HPack+--     will warn in the first case but it's pretty easy to miss.+--+-- @+-- executables:+--   my-awesome-executable+--     main:                Main.hs+--     verbatim:+--       x-build-programs: "cmake,prof"   (a)+--     ghc-options:+--     - ...+--     verbatim:+--       x-build-programs: "someOtherDep" (b)+-- @+--+-- @+-- executables:+--   my-awesome-executable+--     main:                Main.hs+--     when:+--     - condition: os(linux)+--       verbatim:+--         x-build-programs: "cmake,prof" (a)+--     ghc-options:+--     - ...+--     verbatim:+--       x-build-programs: "someOtherDep" (b)+-- @+--++-- $limitations+-- The biggest limitations of this library is it cannot do any kind of version+-- checking of external programs. For example, it is currently not possible to+-- depend on eg. @cmake > 3.0.0@. It simply looks around in the environment for+-- the first executable that matches the name.+--+-- The 'Program' datastructure provided by Cabal is a+-- lot richer providing a way of extracting versions and doing some post+-- configuration, see the builtin+-- <https://hackage.haskell.org/package/Cabal-3.0.0.0/docs/Distribution-Simple-Program.html#v:tarProgram tar> as+-- an example. But it is also more complicated use.