Skip to content

Commit

Permalink
Add pre and post build hooks
Browse files Browse the repository at this point in the history
Run a program (named "preBuildHook") before doing a package build and
another program (named "postBuildHook") after the package is built.
The exit code from the pre-build hook is passed to the post-build
hook.

These programs are project local and need to be in the `cabalHooks`
directory which is in the same directory as the `cabal.project` file.

Co-authored: Moritz Angermann <[email protected]>
  • Loading branch information
erikd committed May 10, 2024
1 parent 214acd8 commit b5c7050
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ import qualified Data.ByteString.Lazy.Char8 as LBS.Char8
import qualified Data.List.NonEmpty as NE

import Control.Exception (ErrorCall, Handler (..), SomeAsyncException, assert, catches)
import System.Directory (canonicalizePath, createDirectoryIfMissing, doesDirectoryExist, doesFileExist, removeFile)
import System.Directory (canonicalizePath, createDirectoryIfMissing, doesDirectoryExist, doesFileExist, getCurrentDirectory, removeFile)
import System.FilePath (dropDrive, normalise, takeDirectory, (<.>), (</>))
import System.IO (Handle, IOMode (AppendMode), withFile)
import System.Semaphore (SemaphoreName (..))
Expand Down Expand Up @@ -679,7 +679,38 @@ buildAndInstallUnpackedPackage
runConfigure
PBBuildPhase{runBuild} -> do
noticeProgress ProgressBuilding
hooksDir <- (</> "cabalHooks") <$> getCurrentDirectory
-- run preBuildHook. If it returns with 0, we assume the build was
-- successful. If not, run the build.
preCode <-
rawSystemExitCode
verbosity
(Just srcdir)
(hooksDir </> "preBuildHook")
[ (unUnitId $ installedUnitId rpkg)
, (getSymbolicPath srcdir)
, (getSymbolicPath builddir)
]
Nothing
`catchIO` (\_ -> pure (ExitFailure 10))
-- Regardless of whether the preBuildHook exists or not, or whether it returned an
-- error or not, we want to run the build command.
-- If the preBuildHook downloads a cached version of the build products, the following
-- should be a NOOP.
runBuild
-- not sure, if we want to care about a failed postBuildHook?
void $
rawSystemExitCode
verbosity
(Just srcdir)
(hooksDir </> "postBuildHook")
[ (unUnitId $ installedUnitId rpkg)
, (getSymbolicPath srcdir)
, (getSymbolicPath builddir)
, show preCode
]
Nothing
`catchIO` (\_ -> pure (ExitFailure 10))
PBHaddockPhase{runHaddock} -> do
noticeProgress ProgressHaddock
runHaddock
Expand Down
14 changes: 14 additions & 0 deletions changelog.d/pr-9899
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
synopsis: Add pre and post build hooks
packages: cabal-install
prs: #9899
issues: #9892
significance: significant

description: {

- Run a program (named "preBuildHook") before doing a package build and another program
(named "postBuildHook") after the package is built.
- These programs are project local and need to be in the `cabalHooks` directory which is
in the same directory as the `cabal.project` file.
- The absence of these programs will be ignored.
}
60 changes: 60 additions & 0 deletions doc/build-hooks.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
Build Hooks
===========

Build hooks are programs that are run before (pre-build hook) and
after (post-build hook) a package (including package dependencies)
is built. The hooks are completely generic and can even be absent
(their absence is ignored). Regardless of the return code of the
pre-build hook, the normal build is executed. In the case where
the pre-build hook provides a pre-built version of what the build
step would provide, the build step is still run, but should be
little more than a NOOP.

Build hooks are project local rather than global to the user
because a single user may want to use one set of hooks in one
project and another set of hoos (or even none at all) for another
project.


Possible Use Cases
------------------

Possible use cases include:

* Fine grained benchmarking of individual package build times.
* Build product caching.


Location of Hook Files
----------------------

The two hook files are `cabalHooks/preBuildHook` and
`cabalHooks/postBuildHook` where the `cabalHooks` directory is in
the same directory as the `cabal.project` file. On UNIX style
systems, these hooks need to be marked as user executable programs.


Hook Parameters Exit Codes
--------------------------

The pre-build hook is passed three parameters; the unit id (from cabal),
the source directory and the build directory. The post-build hook is
passed the same three parameters, plus the exit code of the pre-build
hook.

The exit codes for the two hooks are ignored by cabal apart from cabal
capturing the exit code for the pre-build hook and passing it to the
post-build hook.


Security Considerations
-----------------------

These build hooks are generic executable programs. They can potentially
be malicious. For example, one might clone a Haskell project from
say Github, that includes malicious build hooks so that when the user runs
`cabal build all` these hooks will be run as the user. The most obvious
malicious behaviour would be to delete all the user's files.

For this reason, it is highly advisable to check for the existence
of and the contents of any build hook files.
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Welcome to the Cabal User Guide
how-to-run-in-windows
how-to-use-backpack
how-to-report-bugs
build-hooks

.. toctree::
:caption: Cabal Reference
Expand Down

0 comments on commit b5c7050

Please sign in to comment.