| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Velma
Description
Velma is a Haskell package that makes it easy to automatically add files
to exposed-modules and other-modules in Cabal package descriptions.
Motivation
When working on a Haskell application, it can get tedious to update the
package description (*.cabal file) every time you add, rename, or remove a
module. What's worse is that Cabal can clearly figure this out on its own
since it warns you about it!
<no location info>: warning: [-Wmissing-home-modules]
These modules are needed for compilation but not listed in your .cabal file's other-modules:
Velma.SymbolicPathSo what gives? The package description is in an unfortunate situation: It's meant to be a human writable file, but it's also meant to be machine readable. When someone uploads a package to Hackage, it's important that all of that package's modules be known statically.
But many Haskell projects are never going to be uploaded to Hackage, and the list of exposed modules is essentially just every Haskell file in some directory. That's the problem that Velma aims to solve.
Usage
Velma is implemented as a custom setup script. Read more about them here: https://cabal.readthedocs.io/en/3.6/cabal-package.html#custom-setup-scripts.
To use Velma, you need to do a few things:
Change your build type from
SimpletoCustom:-- *.cabal build-type: Custom
If your
*.cabalfile does not already have abuild-typefield then just add it and set it toCustom.If you're already using a custom setup script, then you probably know what you're doing. You'll probably want to integrate Velma's
confHookinto your custom setup.Add a
custom-setupstanza:-- *.cabal custom-setup setup-depends: base, Cabal, velmaIf you're using
cabal-installthen you can remove theCabaldependency. For some reason Stack requires it.Add
Velma.Discoverto yourexposed-modulesorother-modules:-- *.cabal library exposed-modules: Velma.DiscoverVelma will only discover modules in places where
Velma.Discoveris present. That means you can explicitly list yourexposed-modulesbut let Velma discover yourother-modules. Or you can use Velma only for your test suite. It's up to you!Create a
Setup.hsfile:-- Setup.hs import Velma main = defaultMain
Limitations
- Only
*.hsfiles are discovered.
- All conditionals are ignored.
- The
cabal sdistcommand will not automatically discover modules. This will likely lead to an error such as this: "Setup.hs: Error: Could not find module: Velma.Discover with any suffix: [...]. If the module is autogenerated it should be added to 'autogen-modules'." https://github.com/haskell/cabal/issues/3424 - The
stack buildcommand will generate warnings about missing modules. This warning is safe to ignore. Unfortunately it's visually noisy and there's no way to disable it. https://github.com/commercialhaskell/stack/issues/1881
Synopsis
- defaultMain :: IO ()
- userHooks :: UserHooks
- confHook :: (GenericPackageDescription, HookedBuildInfo) -> ConfigFlags -> IO LocalBuildInfo
- discover :: GenericPackageDescription -> IO GenericPackageDescription
- discoverWith :: Monad m => (FilePath -> m [FilePath]) -> GenericPackageDescription -> m GenericPackageDescription
- discoverLibrary :: Monad m => (FilePath -> m [FilePath]) -> Library -> m Library
- discoverForeignLib :: Applicative m => (FilePath -> m [FilePath]) -> ForeignLib -> m ForeignLib
- discoverExecutable :: Applicative m => (FilePath -> m [FilePath]) -> Executable -> m Executable
- discoverTestSuite :: Applicative m => (FilePath -> m [FilePath]) -> TestSuite -> m TestSuite
- discoverBenchmark :: Applicative m => (FilePath -> m [FilePath]) -> Benchmark -> m Benchmark
- discoverComponent :: (HasBuildInfo a, Applicative m) => Lens' a [ModuleName] -> (a -> [ModuleName]) -> (FilePath -> m [FilePath]) -> a -> m a
- getModuleNames :: (HasBuildInfo a, Applicative m) => (FilePath -> m [FilePath]) -> a -> m (Set ModuleName)
- getHsSourceDirs :: HasBuildInfo a => a -> [FilePath]
- filePathToModuleName :: FilePath -> Maybe ModuleName
- listDirectoryRecursively :: FilePath -> IO [FilePath]
- concatM :: Monad m => [a -> m a] -> a -> m a
- condTreeData :: Lens' (CondTree v c a) a
- maybeRemove :: Eq a => a -> [a] -> Maybe [a]
- overF :: Functor f => Lens' s a -> (a -> f a) -> s -> f s
- withDefault :: Foldable t => t a -> t a -> t a
Documentation
defaultMain :: IO () Source #
The default entrypoint for this custom setup script. This calls Cabal's
defaultMainWithHooks with our custom userHooks.
If you're trying to use Velma in your own project, you should create a
Setup.hs file like this:
-- Setup.hs import Velma main = defaultMain
confHook :: (GenericPackageDescription, HookedBuildInfo) -> ConfigFlags -> IO LocalBuildInfo Source #
Calls discover before handing things off to the confHook from
Cabal's simpleUserHooks.
discover :: GenericPackageDescription -> IO GenericPackageDescription Source #
Simply calls discoverWith with listDirectoryRecursively.
Arguments
| :: Monad m | |
| => (FilePath -> m [FilePath]) | |
| -> GenericPackageDescription | |
| -> m GenericPackageDescription |
Discovers modules in all of the components of this package description.
You can think of this function as calling discoverComponent for each
component: library, sub-libraries, foreign libraries, executables, test
suites, and benchmarks.
Thin wrapper around discoverComponent for libraries.
Arguments
| :: Applicative m | |
| => (FilePath -> m [FilePath]) | |
| -> ForeignLib | |
| -> m ForeignLib |
Thin wrapper around discoverComponent for foreign libraries.
Arguments
| :: Applicative m | |
| => (FilePath -> m [FilePath]) | |
| -> Executable | |
| -> m Executable |
Thin wrapper around discoverComponent for executables.
Arguments
| :: Applicative m | |
| => (FilePath -> m [FilePath]) | |
| -> TestSuite | |
| -> m TestSuite |
Thin wrapper around discoverComponent for test suites.
Arguments
| :: Applicative m | |
| => (FilePath -> m [FilePath]) | |
| -> Benchmark | |
| -> m Benchmark |
Thin wrapper around discoverComponent for benchmarks.
Arguments
| :: (HasBuildInfo a, Applicative m) | |
| => Lens' a [ModuleName] | Typically something like |
| -> (a -> [ModuleName]) | This function is used to get a list of module names to avoid
discovering. For example if you're populating |
| -> (FilePath -> m [FilePath]) | |
| -> a | |
| -> m a |
Discovers modules in the given component, using the provided lens to select which field to update. This is the main workhorse of the package.
Arguments
| :: (HasBuildInfo a, Applicative m) | |
| => (FilePath -> m [FilePath]) | |
| -> a | |
| -> m (Set ModuleName) |
Gets module names for the given component, using the provided function to
list directory contents. This basically just glues together
getHsSourceDirs, listDirectoryRecursively, and filePathToModuleName.
getHsSourceDirs :: HasBuildInfo a => a -> [FilePath] Source #
Gets hs-source-dirs from the given component.
- If
hs-source-dirsisn't set (or is empty), this will return the inferred directory, which is the current directory ("."). - Duplicates are removed from the result using
nubOrd. - This should probably return
SymbolicPathvalues, but that type was only introduced in recent versions (>= 3.6) of Cabal.
filePathToModuleName :: FilePath -> Maybe ModuleName Source #
Attempts to convert a FilePath into a ModuleName. This
works by stripping certain extensions, then converting directory separators
into module separators, and finally trying to parse that as a module name.
>>>filePathToModuleName "Velma.hs"Just (ModuleName "Velma")>>>filePathToModuleName "Velma/SymbolicPath.hs"Just (ModuleName "Velma.SymbolicPath")>>>filePathToModuleName "README.markdown"Nothing>>>filePathToModuleName "library/Velma.hs"Nothing
listDirectoryRecursively :: FilePath -> IO [FilePath] Source #
Lists all of the directory contents recursively. The returned file paths
will include the directory prefix, unlike listDirectory. For
example:
>>>listDirectoryRecursively "source/library"["source/library/Velma.hs","source/library/Velma/SymbolicPath.hs"]
concatM :: Monad m => [a -> m a] -> a -> m a Source #
Applies all of the functions left-to-right using (>=>).
>>>let printAnd f x = do { putStrLn $ "x = " <> show x; pure $ f x }>>>concatM [ printAnd (+ 2), printAnd (* 2) ] 3x = 3 x = 5 10
condTreeData :: Lens' (CondTree v c a) a Source #
A lens for the condTreeData field.
maybeRemove :: Eq a => a -> [a] -> Maybe [a] Source #
Attempts to remove an element from the list. If it succeeds, returns the
list without that element. If it fails, returns Nothing.
>>>maybeRemove 'b' "abc"Just "ac">>>maybeRemove 'z' "abc"Nothing
Note that only the first matching element is removed.
>>>maybeRemove 'b' "abcb"Just "acb"
overF :: Functor f => Lens' s a -> (a -> f a) -> s -> f s Source #
Like over except the modification function can perform arbitrary
effects.
>>>overF _2 (Just . (+ 2)) ('a', 3)Just ('a',5)>>>overF _2 (const Nothing) ('a', 3)Nothing