title | author |
---|---|
Print version information in Haskell programs |
Stefan Klinger <https://stefan-klinger.de/> |
Note: This is much more general than only git hashes and version information, they are just one use case.
The compiled binary must provide the following information:
-
The version number it was assigned in the cabal file.
-
The git hash that represents the latest commit before compilation, including “dirtiness”. Also, its date.
-
The time and date the binary was compiled.
The easiest one is compilation time. Using Template Haskell, the current time can easily be calculated at compile time, and then spliced into the program.
This can be accessed by the magic (and largely undocumented) API
provided in an autogenerated module named Paths_…
Building my application around RIO, I was hoping to get along without
depending on base
at all. Well, o.k., I'd accept Setup.hs
to
depend on base
, assuming this is code running in a pre-compilation
situation and has little to do with the inner workings of my program.
But the Paths_…
module thwarts that plan
module Paths_versionInfo ( … ) where
import qualified Control.Exception as Exception
import Data.Version (Version(..))
import System.Environment (getEnv)
import Prelude
and that is code compiled into the resulting binary. I can live with it, but it's not extra cool, or is it?
Wait, there's a package for that: gitrev — but that just don't cut the mustard.
Obviously, if the git repository is not available at compile time, then there's a problem. This is a legit scenario though, I might want to publish the source without revealing my history of fallacies and transgressions.
Asking for a git hash introduces a dependency on the SCM. And
consequently, Cabal makes this problem obvious when installing a
program that uses gitrev: The build happens in a temporary
directory (not containing the .git
subdirectory that was available
during building), and thus reaching out to git
fails. gitrev just
puts UNKNOWN
where the hash should be.
But still, I think it is legit to ask what commit a binary (or source distribution) was created from. Anyways, the customer asks for it!
Ultimately, after much transgression, I've resorted to auto-generating
a module Generated.Early
to convey the hash into the source
distribution used for cabal install
and also for shipping.
This revealed the following insight:
I see two phases at work here. The first is the step from the cloned git repository to the source distribution, and the second is the actual compilation.
Hence, there can be two sorts of generated files:
-
I'll call early generated the files that are not stored in your source repository, but have to be present in a source distribution.
The information about the current git hash would fall in that category, but also any modules or data used by the product, that should be shipped to the customer without shipping its means of creation. Be it out of embarassment, practicality or legal necessity.
-
I'll call late generated the files that are not in the source distribution, but created during compilation
This category is the “classical” compile-time generated stuff, e.g. date of compilation, but also the
Paths_…
module, and other precomputed data used by the product whose computation cannot be done at source distribution.
Obviously, there may be generated files that would fit into either category, trading source distribution size for compile time duration.
From this perspective, the generation of Paths_…
is only a special
case of the late generated files. Allowing the developer to provide
a template for Paths_…
, one could avoid the base
dependency during
compilation.
I do not see Cabal's user interface providing a clear line between these two sorts of auto-generated code. It might be there, somewhere, but I can't put my finger on it. Maybe I just did not find it in the documentation.
-
Just as there is a directory where (late) auto-generated code ends up, it would be nice to have such a location for early stuff too. But I'm entirely unsure what that could be.
-
It is not entirely clear how Cabal could detect a pre-sdist situation. My best guess is to leave this to the developer of the package, since a simple test may be sufficient.
-
Cabal does not know about early generated code. Thus, the package is reported to be broken
$ cabal check Warning: These warnings may cause trouble when distributing the package: Warning: In 'extra-source-files': the pattern 'src/Generated/Early.hs' does not match any files.
although
cabal build
will fix this. -
I had to write my own
Setup.hs
. A more sane approach would be Cabal providing early and late generation hooks as outlined above. -
After cloning from git, it is now mandatory to
cabal build
beforecabal install
, otherwisecabal: filepath wildcard 'src/Generated/Early.hs' does not match any files.
But installation from a source distribution tarball is fine, because that will contain
gitinfo
. -
I don't like
Paths_…
pulling inbase
. If that could be built from a template, the developer might have more control over the dependencies. Such a templating mechanism would also be useful for early generated code.