diff --git a/Cabal/src/Distribution/Simple/Build.hs b/Cabal/src/Distribution/Simple/Build.hs index 7f8b9f0f069..4c7dcbf681f 100644 --- a/Cabal/src/Distribution/Simple/Build.hs +++ b/Cabal/src/Distribution/Simple/Build.hs @@ -1168,7 +1168,7 @@ runPreBuildHooks pbcRules = do let verbosity = mkVerbosity verbHandles $ buildingWhatVerbosity what (rules, mons) <- SetupHooks.computeRules verbosity pbci pbcRules - SetupHooks.executeRules verbosity lbi tgt rules + SetupHooks.executeRules verbosity lbi tgt mons rules return mons -- | Built-in pre-build 'SetupHooks' for a given 'BuildType'. diff --git a/Cabal/src/Distribution/Simple/BuildPaths.hs b/Cabal/src/Distribution/Simple/BuildPaths.hs index 54472859339..989c077453d 100644 --- a/Cabal/src/Distribution/Simple/BuildPaths.hs +++ b/Cabal/src/Distribution/Simple/BuildPaths.hs @@ -28,6 +28,7 @@ module Distribution.Simple.BuildPaths , autogenPackageModulesDir , autogenComponentModulesDir , preBuildRulesCacheFile + , preBuildMonitorManifestFile , autogenPathsModuleName , autogenPackageInfoModuleName , cppHeaderName @@ -170,6 +171,18 @@ preBuildRulesCacheFile preBuildRulesCacheFile lbi clbi = componentBuildDir lbi clbi makeRelativePathEx "setup-hooks-rules.cache" +-- | The path to the pre-build monitor manifest file for a component. +-- +-- This file is written after running pre-build rules, so that external +-- tools (e.g. HLS) can discover which files to watch for changes and which +-- files were generated by the rules. See 'writePreBuildMonitorManifest'. +preBuildMonitorManifestFile + :: LocalBuildInfo + -> ComponentLocalBuildInfo + -> SymbolicPath Pkg File +preBuildMonitorManifestFile lbi clbi = + componentBuildDir lbi clbi makeRelativePathEx "pre-build-monitors" + -- NB: Look at 'checkForeignDeps' for where a simplified version of this -- has been copy-pasted. diff --git a/Cabal/src/Distribution/Simple/SetupHooks/Internal.hs b/Cabal/src/Distribution/Simple/SetupHooks/Internal.hs index 3d767f6fdb8..b2446db85fc 100644 --- a/Cabal/src/Distribution/Simple/SetupHooks/Internal.hs +++ b/Cabal/src/Distribution/Simple/SetupHooks/Internal.hs @@ -82,6 +82,7 @@ module Distribution.Simple.SetupHooks.Internal -- ** Executing build rules , executeRules , executeRulesUserOrSystem + , writePreBuildMonitorManifest -- ** HookedBuildInfo compatibility code , hookedBuildInfoComponents @@ -94,6 +95,7 @@ import Prelude () import Distribution.Compat.Lens ((.~)) import Distribution.PackageDescription +import Distribution.Pretty (prettyShow) import Distribution.Simple.BuildPaths import Distribution.Simple.Compiler (Compiler (..)) import Distribution.Simple.Errors @@ -135,7 +137,13 @@ import qualified Data.Map as Map import Data.Monoid (Ap (..)) import qualified Data.Set as Set -import System.Directory (doesFileExist, getModificationTime) +import System.Directory + ( createDirectoryIfMissing + , doesFileExist + , getModificationTime + , makeAbsolute + ) +import System.FilePath (takeDirectory) -------------------------------------------------------------------------------- -- SetupHooks @@ -850,6 +858,8 @@ executeRules :: Verbosity -> LocalBuildInfo -> TargetInfo + -> [MonitorFilePath] + -- ^ files monitored by the pre-build rules -> Map RuleId Rule -> IO () executeRules = @@ -872,9 +882,11 @@ executeRulesUserOrSystem -> Verbosity -> LocalBuildInfo -> TargetInfo + -> [MonitorFilePath] + -- ^ files monitored by the pre-build rules -> Map RuleId (RuleData userOrSystem) -> IO () -executeRulesUserOrSystem scope runDepsCmdData runCmdData verbosity lbi tgtInfo allRules = do +executeRulesUserOrSystem scope runDepsCmdData runCmdData verbosity lbi tgtInfo monitors allRules = do -- Load the rule cache from the previous build. -- Used to detect when rule definitions have changed. oldRules <- handleDoesNotExist Map.empty $ do @@ -995,6 +1007,19 @@ executeRulesUserOrSystem scope runDepsCmdData runCmdData verbosity lbi tgtInfo a errorOut $ MissingRuleOutputs (toRuleBinary r) missingResults -- Save the current rules to the cache for use in the next build. structuredEncodeFile rulesCacheFile allRules + -- Write the monitor manifest for external tools (e.g. HLS). + let allFileDeps = + [ loc + | (rId, Rule{staticDependencies}) <- Map.toList allRules + , let dynDeps = maybe [] fst (Map.lookup rId dynDepsEdges) + , FileDependency loc <- staticDependencies ++ dynDeps + ] + allOutputs = + [ loc + | Rule{results} <- Map.elems allRules + , loc <- NE.toList results + ] + writePreBuildMonitorManifest lbi tgtInfo monitors allFileDeps allOutputs where toRuleBinary :: RuleData userOrSystem -> RuleBinary toRuleBinary = case scope of @@ -1009,6 +1034,65 @@ executeRulesUserOrSystem scope runDepsCmdData runCmdData verbosity lbi tgtInfo a SetupHooksException $ RulesException e +-- | Write the pre-build monitor manifest for a component. +-- +-- This plain-text file (stored at 'preBuildMonitorManifestFile') is intended +-- for external tools such as HLS that invoke @cabal build@ but do not link +-- against the Cabal library. It records enough information for such tools +-- to detect when pre-build rules need to be re-computed or re-run, as per +-- the 'SetupHooks' documentation in @Cabal-hooks@. +-- +-- The manifest contains three sections: +-- +-- * @[monitors]@: values monitored by the computation of pre-build rules. +-- * @[inputs]@: file dependencies of pre-build rules (static & dynamic). +-- * @[outputs]@: file outputs of pre-build rules. +-- +-- All relative paths in the manifest are relative to the @pkg-root@ listed in +-- the file header. +writePreBuildMonitorManifest + :: LocalBuildInfo + -> TargetInfo + -> [MonitorFilePath] + -- ^ recompute pre-build rules when these change + -> [Location] + -- ^ combined static and dynamic file dependencies of pre-build rules + -> [Location] + -- ^ outputs (files generated by the pre-build rules) + -> IO () +writePreBuildMonitorManifest lbi tgtInfo monitors fileDeps outputs = do + pkgRoot <- makeAbsolute (maybe "." getSymbolicPath (mbWorkDirLBI lbi)) + let clbi = targetCLBI tgtInfo + manifestPath = interpretSymbolicPathLBI lbi (preBuildMonitorManifestFile lbi clbi) + content = + unlines $ + concat + [ + [ "pre-build-monitors-v1" + , "pkg-root:" ++ pkgRoot + ] + , ["", "[monitors]"] + , map serialiseMonitor monitors + , ["", "[inputs]"] + , map (getSymbolicPath . location) fileDeps + , ["", "[outputs]"] + , map (getSymbolicPath . location) outputs + ] + createDirectoryIfMissing True (takeDirectory manifestPath) + writeFile manifestPath content + where + serialiseMonitor (MonitorFile kf kd path) = + "file:" ++ showKindFile kf ++ ":" ++ showKindDir kd ++ ":" ++ path + serialiseMonitor (MonitorFileGlob kf kd glob) = + "glob:" ++ showKindFile kf ++ ":" ++ showKindDir kd ++ ":" ++ prettyShow glob + showKindFile FileExists = "exists" + showKindFile FileModTime = "modtime" + showKindFile FileHashed = "hashed" + showKindFile FileNotExists = "notexists" + showKindDir DirExists = "exists" + showKindDir DirModTime = "modtime" + showKindDir DirNotExists = "notexists" + directRuleDependencyMaybe :: Rule.Dependency -> Maybe RuleId directRuleDependencyMaybe (RuleDependency dep) = Just $ outputOfRule dep directRuleDependencyMaybe (FileDependency{}) = Nothing diff --git a/changelog.d/rules-manifest.md b/changelog.d/rules-manifest.md new file mode 100644 index 00000000000..6392fc0bb04 --- /dev/null +++ b/changelog.d/rules-manifest.md @@ -0,0 +1,22 @@ +--- +synopsis: Write pre-build rules manifest file +packages: [Cabal, cabal-install] +prs: 11776 +issues: +--- + +After executing pre-build rules for a component, `Cabal` now writes a manifest +file to the `pre-build-monitors` file in the component's build directory. + +This manifest contains: + + 1. The files that are monitored by the pre-build rules computation. When any + of these change, we need to re-run the computation of pre-build rules. + 2. File dependencies of the pre-build rules. When any of these change, we + need to re-run rules (the recompilation checking logic will take care to + only re-run stale rules). + 3. Rule outputs, i.e. files generated by the pre-build rules. + +Writing this information to a file makes it available to external tools such as +HLS without needing to link against the Cabal library. This allows external +tools to know when to re-run the pre-build rules. diff --git a/hooks-exe/cli/Distribution/Client/SetupHooks/CallHooksExe.hs b/hooks-exe/cli/Distribution/Client/SetupHooks/CallHooksExe.hs index 6ef2313496c..a85150d7d0e 100644 --- a/hooks-exe/cli/Distribution/Client/SetupHooks/CallHooksExe.hs +++ b/hooks-exe/cli/Distribution/Client/SetupHooks/CallHooksExe.hs @@ -223,7 +223,7 @@ runExternalPreBuildRules verbHandles hooksExe DynamicRuleCommands {} -> hook "runPreBuildRuleDeps" (rId, cmd) ) ( \ rId cmd -> hook "runPreBuildRule" (rId, cmd) ) - verbosity lbi tgt rulesMap + verbosity lbi tgt monitors rulesMap return monitors -- | The path to the external hooks executable.