Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pre and post build hooks #9899

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,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, onException)
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 @@ -685,7 +685,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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I argue we should. The author of the script can always hide errors of whatever commands they're executing inside the hook.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you propose?

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 hooks (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 @@ -19,6 +19,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
Loading