diff --git a/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs b/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs index 7d9f34a8e8b..d62e32f55c4 100644 --- a/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs +++ b/cabal-install/src/Distribution/Client/ProjectBuilding/UnpackedPackage.hs @@ -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 (..)) @@ -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 diff --git a/changelog.d/pr-9899 b/changelog.d/pr-9899 new file mode 100644 index 00000000000..64f5f3fc619 --- /dev/null +++ b/changelog.d/pr-9899 @@ -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. +} diff --git a/doc/build-hooks.rst b/doc/build-hooks.rst new file mode 100644 index 00000000000..193adc29707 --- /dev/null +++ b/doc/build-hooks.rst @@ -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. diff --git a/doc/index.rst b/doc/index.rst index 0b5407e8580..1af7b776429 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -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