content: module Rules (buildRules, oracleRules, packageTargets, topLevelTargets) where
  
  import qualified Hadrian.Oracles.ArgsHash
  import qualified Hadrian.Oracles.Cabal.Rules
  import qualified Hadrian.Oracles.DirectoryContents
  import qualified Hadrian.Oracles.Path
  import qualified Hadrian.Oracles.TextFile
  
  import Expression
  import qualified Oracles.ModuleFiles
  import Packages
  import qualified Rules.BinaryDist
  import qualified Rules.Compile
  import qualified Rules.Configure
  import qualified Rules.Dependencies
  import qualified Rules.Documentation
  import qualified Rules.Generate
  import qualified Rules.Gmp
  import qualified Rules.Libffi
  import qualified Rules.Library
  import qualified Rules.Program
  import qualified Rules.Register
  import Settings
  import Target
  import UserSettings
  import Utilities
  
  allStages :: [Stage]
  allStages = [minBound .. maxBound]
  
  -- | This rule calls 'need' on all top-level build targets that Hadrian builds
  -- by default, respecting the 'stage1Only' flag.
  topLevelTargets :: Rules ()
  topLevelTargets = action $ do
      verbosity <- getVerbosity
      when (verbosity >= Loud) $ do
          (libraries, programs) <- partition isLibrary <$> stagePackages Stage1
          libNames <- mapM (name Stage1) libraries
          pgmNames <- mapM (name Stage1) programs
          putNormal . unlines $
              [ "| Building Stage1 libraries: " ++ intercalate ", " libNames
              , "| Building Stage1 programs : " ++ intercalate ", " pgmNames ]
      let buildStages = [Stage0, Stage1] ++ [Stage2 | not stage1Only]
      targets <- concatForM buildStages $ \stage -> do
          packages <- stagePackages stage
          mapM (path stage) packages
      need targets
    where
      -- either the package database config file for libraries or
      -- the programPath for programs. However this still does
      -- not support multiple targets, where a cabal package has
      -- a library /and/ a program.
      path :: Stage -> Package -> Action FilePath
      path stage pkg | isLibrary pkg = pkgConfFile (vanillaContext stage pkg)
                     | otherwise     = programPath =<< programContext stage pkg
      name :: Stage -> Package -> Action String
      name stage pkg | isLibrary pkg = return (pkgName pkg)
                     | otherwise     = programName (vanillaContext stage pkg)
  
  -- TODO: Get rid of the @includeGhciLib@ hack.
  -- | Return the list of targets associated with a given 'Stage' and 'Package'.
  -- By setting the Boolean parameter to False it is possible to exclude the GHCi
  -- library from the targets, and avoid configuring the package to determine
  -- whether GHCi library needs to be built for it. We typically want to set
  -- this parameter to True, however it is important to set it to False when
  -- computing 'topLevelTargets', as otherwise the whole build gets sequentialised
  -- because packages are configured in the order respecting their dependencies.
  packageTargets :: Bool -> Stage -> Package -> Action [FilePath]
  packageTargets includeGhciLib stage pkg = do
      let context = vanillaContext stage pkg
      activePackages <- stagePackages stage
      if pkg `notElem` activePackages
      then return [] -- Skip inactive packages.
      else if isLibrary pkg
          then do -- Collect all targets of a library package.
              let pkgWays = if pkg == rts then getRtsWays else getLibraryWays
              ways  <- interpretInContext context pkgWays
              libs  <- mapM (pkgLibraryFile . Context stage pkg) ways
              more  <- libraryTargets includeGhciLib context
              setup <- pkgSetupConfigFile context
              return $ [setup] ++ libs ++ more
          else do -- The only target of a program package is the executable.
              prgContext <- programContext stage pkg
              prgPath    <- programPath prgContext
              return [prgPath]
  
  packageRules :: Rules ()
  packageRules = do
      -- We cannot register multiple GHC packages in parallel. Also we cannot run
      -- GHC when the package database is being mutated by "ghc-pkg". This is a
      -- classic concurrent read exclusive write (CREW) conflict.
      let maxConcurrentReaders = 1000
      packageDb <- newResource "package-db" maxConcurrentReaders
      let readPackageDb  = [(packageDb, 1)]
          writePackageDb = [(packageDb, maxConcurrentReaders)]
  
      let contexts        = liftM3 Context        allStages knownPackages allWays
          vanillaContexts = liftM2 vanillaContext allStages knownPackages
  
      -- TODO: we might want to look into converting more and more
      --       rules to the style introduced in Rules.Library in
      --       https://github.com/snowleopard/hadrian/pull/571,
      --       where "catch-all" rules are used to "catch" the need
      --       for library files, and we then use parsec parsers to
      --       extract all sorts of information needed to build them, like
      --       the package, the stage, the way, etc.
  
      forM_ contexts (Rules.Compile.compilePackage readPackageDb)
  
      Rules.Program.buildProgram readPackageDb
  
      forM_ [Stage0 .. ] $ \stage ->
          -- we create a dummy context, that has the correct state, but contains
          -- @dummyPackage@ as a... dummy package. The package isn't accessed but the record
          -- need to be set properly. @undefined@ is not an option as it ends up
          -- being forced.
          Rules.Register.registerPackage writePackageDb (Context stage dummyPackage vanilla)
  
      forM_ vanillaContexts $ mconcat
          [ Rules.Register.configurePackage
          , Rules.Dependencies.buildPackageDependencies readPackageDb
          , Rules.Documentation.buildPackageDocumentation
          , Rules.Generate.generatePackageCode ]
  
  buildRules :: Rules ()
  buildRules = do
      Rules.BinaryDist.bindistRules
      Rules.Configure.configureRules
      Rules.Generate.copyRules
      Rules.Generate.generateRules
      Rules.Gmp.gmpRules
      Rules.Libffi.libffiRules
      Rules.Library.libraryRules
      packageRules
  
  oracleRules :: Rules ()
  oracleRules = do
      Hadrian.Oracles.ArgsHash.argsHashOracle trackArgument getArgs
      Hadrian.Oracles.Cabal.Rules.cabalOracle
      Hadrian.Oracles.DirectoryContents.directoryContentsOracle
      Hadrian.Oracles.Path.pathOracle
      Hadrian.Oracles.TextFile.textFileOracle
      Oracles.ModuleFiles.moduleFilesOracle
  