I would like to Cabalize a Haskell project in order to upload it to Hackage. It consists of one standard .hs file which builds a bootstrap executable, and eight nonstandard .lhs files which are processed by the bootstrap executable to yield the .hs files which build the production executable.
In other words, my project is a custom literate preprocessor (with HTML markup, code grooming, here documents) which is written in itself. It depends on a bootstrap version which can translate the literate code for the production version, but which is not suitable for general use. So I do not want the bootstrap version to be a separate package; it is meant to be used only once, here.
I have been using a Makefile, which is possible in Cabal but not in the spirit of Cabal. What is a clean way to do this, that takes maximal advantage of the generality of Cabal? I have not seen any suggestions for this in the online documentation, short of either rewriting Distribution.Simple or using a Makefile.
I think that releasing your bootstrap executable as a separate package is the easiest solution, and there are instructions available on how to integrate your custom preprocessor with Cabal.
If you don't want to do that, try making your preprocessor part of Setup.hs (don't forget to set the build type to Custom!). You can run your preprocessor during the configure step. Look at how wxcore does this.
Here's a working example (patterned after wxcore):
Setup.hs:
import Distribution.Simple
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.Setup
import Distribution.PackageDescription
import Control.Monad (forM_)
import System.Directory (copyFile)
import System.FilePath (replaceExtension)
main :: IO ()
main = defaultMainWithHooks simpleUserHooks { confHook = myConfHook }
-- Replace 'copyFile' with more complicated logic.
runMyPreprocessor :: FilePath -> IO ()
runMyPreprocessor abcFile = copyFile abcFile (replaceExtension abcFile ".hs")
myConfHook :: (GenericPackageDescription, HookedBuildInfo) -> ConfigFlags
-> IO LocalBuildInfo
myConfHook (pkgDesc, hbi) flags = do
let extraSrc = extraSrcFiles . packageDescription $ pkgDesc
forM_ extraSrc runMyPreprocessor
confHook simpleUserHooks (pkgDesc, hbi) flags
my-preprocessor.cabal:
name: my-preprocessor
version: 0.1.0.0
build-type: Custom
cabal-version: >=1.8
extra-source-files: Main.abc
executable my-preprocessor
main-is: Main.hs
build-depends: base < 5
Main.abc:
module Main
where
main :: IO ()
main = putStrLn "Hello"
Related
I have a bunch of Haskell files I need to compile with GHC, but some import libraries that don't exist. Is there a way to suppress the compiler error: Could not find module, and only make it throw during runtime? Something like -fdefer-type-errors does, but for imports.
Editing the files is not an option at the moment, and most imports are not even used by the program, so would never throw if it compiled.
You can use cabal's mixins to expose other, existing, modules with the names of the modules you desire to exist. For example you might have a file:
module MyLib (someFunc) where
import Module1
import Module2
someFunc :: IO ()
someFunc = putStrLn "someFunc"
So Module1 and Module2 do not actually exist. But you can point those modules to anything, such as Data.Map and Data.Set using the cabal file:
library
exposed-modules: MyLib
-- Modules included in this library but not exported.
-- other-modules:
-- LANGUAGE extensions used by modules in this package.
-- other-extensions:
build-depends: base ^>=4.14.0.0, containers
hs-source-dirs: src
default-language: Haskell2010
mixins:
containers (Data.Map as Module1, Data.Set as Module2)
There is no option in GHC that allows compilation when an imported module can not be found.
I have installed tagsoup with
cabal v1-install tagsoup
and verified the install with ghc-pkg list | grep tagsoup
However, in my very simple Haskell 8.6.5 program the statement
import Text.HTML.TagSoup
fails with cannot find module 'Text.HTML.TagSoup'
ghc -v is not useful
cabal new-install tagsoup fails with a ton of errors
import Network.HTTP.Conduit
import Text.HTML.TagSoup
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as CL
main :: IO ()
main = do
lbs <- simpleHttp "https://wiki.haskell.org"
print $ show lbs
-- tagsoup code removed
For this kind of a single file use case, I'd recommend using a Stack script. If you add the following two lines to the top of your file:
#!/usr/bin/env stack
-- stack --resolver lts-13.27 script
You can then run stack filename.hs, which will:
Download GHC if necessary
Download and build all dependencies, based on your import list
Use runghc to run your program
More information:
How to Script
Get Started with Haskell
I'm developing a Haskell package and would like to test it as if it were an installed package. That is, I'd like to be able to install it among my other packages and then either import or :m +.
I can successfully build my package and (apparently) can successfully install it using
cabal install
which reports
Resolving dependencies...
Configuring Exos-0.0.1.0...
Building Exos-0.0.1.0...
Installed Exos-0.0.1.0
but all attempts to import the package then fail with something like (e.g. in GHCi)
<no location info>:
Could not find module ‘Exos’
It is not a module in the current program, or in any known package.
even though I can see a populated Exos-0.0.1.0 folder in my Haskell lib directory. (Even "uninstalling" fails with ghc-pkg unregister, which reports ghc-pkg: cannot find package Exos-0.0.1.0.)
How do I install and test a package that I'm developing locally, so that it behaves like part of my collection of installed Haskell packages — specifically so that I can import it (and "uninstall" it) like any other?
My package has the structure
Exos.cabal
Exos/
Cryo/
Exos.hs
Exos/
Display.hs
Core.hs
with (relevant) contents in Exos.cabal
name: Exos
version: 0.0.1.0
build-type: Simple
- ...
library
exposed-modules: Cryo.Exos,
Cryo.Exos.Display
other-modules: Cryo.Exos.Core
-- other-extensions:
build-depends: base >=4.8,
containers >= 0.5.5.1,
split >= 0.2.2,
MissingH >= 1.3.0.1
-- hs-source-dirs:
default-language: Haskell2010
in Exos.hs
module Cryo.Exos
(
f,
g,
otherFunc
) where
import Cryo.Exos.Core
-- ...
in Core.hs
module Cryo.Exos.Core where
-- ...
and in Display.hs
module Cryo.Exos.Display
(
showSomething,
showSomethingElse
) where
import Data.Char
-- ...
import Cryo.Exos.Core
import Cryo.Exos
FWIW, inside my IDE, I can write an and successfully run "Application" with the following section in the above .cabal file
executable enigma-hs
main-is: Main.hs
build-depends: base >=4.8,
Exos
hs-source-dirs: tests
default-language: Haskell2010
and the following (relevant) code in Exos/tests/Main.hs
module Main where
import System.IO
import Cryo.Exos
import Cryo.Exos.Display
main :: IO ()
main = do
putStr $ showSomething
putStr $ showSomethingElse
-- ...
The error is a simple one: If you look at the Main.hs example where you have successfully imported the module you have
import Cryos.Expos
and not
import Expos -- wrong
that's because, the module you are importing is Cryos.Expos from the package Expos.
Something did not make sense with a Cabal package I was developing, and I have boiled the issue down to the following example:
I have the following simple test module:
module Main where
import Test.QuickCheck (quickCheck)
main = quickCheck False
And the following Cabal file in the same directory:
name: project
version: 0.1.0.0
cabal-version: >= 1.10
build-type: Simple
executable project
main-is: Main.hs
build-depends: base, QuickCheck
default-language: Haskell2010
test-suite project-test
type: exitcode-stdio-1.0
main-is: Main.hs
build-depends: base, QuickCheck
default-language: Haskell2010
The only other files in the directory are dist (created by Cabal upon build) and the cabal sandbox files. Both the executable and the test-suite refer to Main.hs, and therefore I would expect to get the same test results when running "cabal run" as when running "cabal test". Apparently, however, that is not the case.
"cabal run" gives:
Preprocessing executable 'project' for project-0.1.0.0...
Running project...
*** Failed! Falsifiable (after 1 test):
which makes sense because the property test 'quickCheck False' should fail. As expected this is the same result I get when running main in ghci.
"cabal test", however, gives:
Test suite project-test: RUNNING...
Test suite project-test: PASS
Test suite logged to: dist/test/project-0.1.0.0-project-test.log
1 of 1 test suites (1 of 1 test cases) passed.
Why the heck does "cabal test" pass the test case, whereas "cabal run" fails it as expected?
quickCheck doesn't exit the program, therefore, it doesn't set the exit code. After all, you could have several quickCheck tests, which should be independent of each other:
main = do
quickCheck prop1 -- should this exit if the test fails?
quickCheck prop2 -- should this exit if the test fails?
quickCheck prop3 -- should this exit if the test fails?
However, you can easily fix this if you a) exit as soon as one of your tests doesn't pass or b) remember whether a single test hasn't passed and then exit with the correct code.
Using only QuickCheck, but no other library
Exit with failure as soon as a test fails
For this, you simply use quickCheckResult, which you can check with isSuccess from Test.QuickCheck.Test. If you want to use your current quickCheck definition, you can use qualified includes to exchange the default implementation with your special one:
import Control.Monad (when)
import System.Exit (exitFailure)
import Test.QuickCheck hiding (quickCheck, quickCheckWith)
import qualified Test.QuickCheck.Test as Q
quickCheckWith :: Testable prop => Args -> prop -> IO ()
quickCheckWith args p = do
success <- fmap Q.isSuccess $ quickCheckWithResult args p
when (not success) $ exitFailure -- exit if the result is a failure
quickCheck :: Testable prop => prop -> IO ()
quickCheck p = quickCheckWith stdArgs p
You should probably use another name though, especially if other people work on the same project. checkOrExit would be plausible.
Return failure code but run all tests
This is essentially the same, but runs all test. You must use quickCheckResult or quickCheckWithResult again:
import Control.Monad (when)
import System.Exit (exitFailure)
import Test.QuickCheck (quickCheckResult)
import Test.QuickCheck.Test (isSuccess)
main :: IO ()
main = do
let tests = [ quickCheckResult prop1
, quickCheckResult prop2
, quickCheckResult prop3
, quickCheckResult prop4
]
success <- fmap (all isSuccess) . sequence $ tests
when (not success) $ exitFailure
Using additional test libraries
While quickCheck is great for property checking, it doesn't provide a complete testing framework. That's where other full-fledged frameworks such as tasty or hspec come in handy. They can take a Testable a and inspect QuickCheck's result accordingly. An example using hspec:
module Main where
import Test.Hspec
import Test.QuickCheck (property)
main = hspec $ do
describe "<method you would like to test>" $
it "<property/assumption you would like to test>" $
property $ False -- quickCheck
This gives the (more verbose) output
<method you would like to test>
- <property/assumption you would like to test> FAILED [1]
1) <method you would like to test> <property/assumption you would like to test>
Falsifiable (after 1 test):
[remark: the used values would be here, but `False` is a boolean]
Randomized with seed 2077617428
Finished in 0.0019 seconds
1 example, 1 failure
Also, it exits with an error code, so your cabal test will recognize this failed test correctly. Those test frameworks also have additional features, which go beyond of the scope of this answer.
TL;DR
QuickCheck doesn't exit your program, but Cabal only inspects the exit code to determine whether the tests have passed. Since any regularly ending Haskell program returns zero (aka no error), you either need to use exitFailure (or something similar), or a framework that uses exitFailure behind the scenes.
Note that this only holds for tests with type: exitcode-stdio-1.0.
Given a freshly scaffolded Yesod application, what is the minimal set of changes necessary to get an executable which acts as a CGI program? A wrapper program is acceptable. If the default executable built by 'cabal build' is a CGI program, what environment variables must be set for it to act as a CGI (as by default it will bind to a port and attempt to serve requests there.)
A similar answer for FastCGI would also be appreciated.
Update your app/main.hs with the following:
import Prelude (IO, (>>=))
import Yesod.Default.Config (fromArgs)
import Yesod.Default.Main (defaultMain)
import Settings (parseExtra)
import Application (makeApplication)
import Network.Wai.Handler.CGI (run)
main :: IO ()
main = fromArgs parseExtra >>= makeApplication >>= run
You'll need to add wai-extra to the dependencies in your cabal file. To use FastCGI instead, replace Network.Wai.Handler.CGI with Network.Wai.Handler.FastCGI and add wai-handler-fastcgi to the dependency list instead.