content: -----------------------------------------------------------------------------
  -- |
  -- Module     : Hadrian.Builder
  -- Copyright  : (c) Andrey Mokhov 2014-2017
  -- License    : MIT (see the file LICENSE)
  -- Maintainer : andrey.mokhov@gmail.com
  -- Stability  : experimental
  --
  -- A typical build system invokes several build tools, or /builders/, such as
  -- compilers, linkers, etc., some of which may be built by the build system
  -- itself. This module defines the 'Builder' type class and a few associated
  -- functions that can be used to invoke builders.
  -----------------------------------------------------------------------------
  module Hadrian.Builder (
      Builder (..), BuildInfo (..), needBuilder, runBuilder,
      runBuilderWithCmdOptions, build, buildWithResources, buildWithCmdOptions,
      getBuilderPath, builderEnvironment, askWithResources
      ) where
  
  import Data.List
  import Development.Shake
  
  import Hadrian.Expression hiding (inputs, outputs)
  import Hadrian.Oracles.ArgsHash
  import Hadrian.Target
  import Hadrian.Utilities
  
  -- | This data structure captures all information relevant to invoking a builder.
  data BuildInfo = BuildInfo {
      -- | Command line arguments.
      buildArgs :: [String],
      -- | Input files.
      buildInputs :: [FilePath],
      -- | Output files.
      buildOutputs :: [FilePath],
      -- | Options to be passed to Shake's 'cmd' function.
      buildOptions :: [CmdOption],
      -- | Resources to be aquired.
      buildResources :: [(Resource, Int)] }
  
  class ShakeValue b => Builder b where
      -- | The path to a builder.
      builderPath :: b -> Action FilePath
  
      -- | Ask the builder for information.
      -- E.g. ask @ghc-pkg@ for package dependencies
      -- capture the @stdout@ result and return it.
      askBuilderWith :: b -> BuildInfo -> Action String
  
      -- | Runtime dependencies of a builder. For example, on Windows GHC requires
      -- the utility @touchy.exe@ to be avilable on a specific path.
      runtimeDependencies :: b -> Action [FilePath]
      runtimeDependencies _ = return []
  
      -- | Run a builder with a given 'BuildInfo'. Also see 'runBuilder'.
      runBuilderWith :: b -> BuildInfo -> Action ()
      runBuilderWith builder buildInfo = do
          let args = buildArgs buildInfo
          needBuilder builder
          path <- builderPath builder
          let msg = if null args then "" else " (" ++ intercalate ", " args ++ ")"
          putBuild $ "| Run " ++ show builder ++ msg
          quietly $ cmd (buildOptions buildInfo) [path] args
  
  -- | Make sure a builder and its runtime dependencies are up-to-date.
  needBuilder :: Builder b => b -> Action ()
  needBuilder builder = do
      path <- builderPath builder
      deps <- runtimeDependencies builder
      need (path : deps)
  
  -- | Run a builder with a specified list of command line arguments, reading a
  -- list of input files and writing a list of output files. A lightweight version
  -- of 'runBuilderWith'.
  runBuilder :: Builder b => b -> [String] -> [FilePath] -> [FilePath] -> Action ()
  runBuilder = runBuilderWithCmdOptions []
  
  -- | Like 'runBuilder' but passes given options to Shake's 'cmd'.
  runBuilderWithCmdOptions :: Builder b => [CmdOption] -> b -> [String] -> [FilePath] -> [FilePath] -> Action ()
  runBuilderWithCmdOptions opts builder args inputs outputs =
      runBuilderWith builder $ BuildInfo { buildArgs      = args
                                         , buildInputs    = inputs
                                         , buildOutputs   = outputs
                                         , buildOptions   = opts
                                         , buildResources = [] }
  
  -- | Build a 'Target' using the list of command line arguments computed from a
  -- given 'Args' expression. Force a rebuild if the argument list has changed
  -- since the last build.
  build :: (Builder b, ShakeValue c) => Target c b -> Args c b -> Action ()
  build = buildWith [] []
  
  -- | Like 'build' but acquires necessary resources.
  buildWithResources :: (Builder b, ShakeValue c) => [(Resource, Int)] -> Target c b -> Args c b -> Action ()
  buildWithResources rs = buildWith rs []
  
  askWithResources :: (Builder b, ShakeValue c) => [(Resource, Int)] -> Target c b -> Args c b -> Action String
  askWithResources rs = askWith rs []
  
  -- | Like 'build' but passes given options to Shake's 'cmd'.
  buildWithCmdOptions :: (Builder b, ShakeValue c) => [CmdOption] -> Target c b -> Args c b -> Action ()
  buildWithCmdOptions = buildWith []
  
  doWith :: (Builder b, ShakeValue c)
         => (b -> BuildInfo -> Action a)
         -> (Target c b -> Action ())
         -> [(Resource, Int)] -> [CmdOption] -> Target c b -> Args c b -> Action a
  doWith f info rs opts target args = do
      needBuilder (builder target)
      argList <- interpret target args
      trackArgsHash target -- Rerun the rule if the hash of argList has changed.
      info target
      verbose <- interpret target verboseCommand
      let quietlyUnlessVerbose = if verbose then withVerbosity Loud else quietly
      quietlyUnlessVerbose $ f (builder target) $
          BuildInfo { buildArgs      = argList
                    , buildInputs    = inputs target
                    , buildOutputs   = outputs target
                    , buildOptions   = opts
                    , buildResources = rs }
  
  buildWith :: (Builder b, ShakeValue c) => [(Resource, Int)] -> [CmdOption] -> Target c b -> Args c b -> Action ()
  buildWith = doWith runBuilderWith runInfo
  
  askWith :: (Builder b, ShakeValue c) => [(Resource, Int)] -> [CmdOption] -> Target c b -> Args c b -> Action String
  askWith = doWith askBuilderWith askInfo
  
  -- | Print out information about the command being executed.
  runInfo :: Show b => Target c b -> Action ()
  runInfo t = putProgressInfo =<< renderAction
      ("Run " ++ show (builder t)) -- TODO: Bring back contextInfo.
      (digest $ inputs  t)
      (digest $ outputs t)
    where
      digest [] = "none"
      digest [x] = x
      digest (x:xs) = x ++ " (and " ++ show (length xs) ++ " more)"
  
  askInfo :: Show b => Target c b -> Action ()
  askInfo t = putProgressInfo =<< renderActionNoOutput
      ("Run " ++ show (builder t)) -- TODO: Bring back contextInfo.
      (digest $ inputs  t)
    where
      digest [] = "none"
      digest [x] = x
      digest (x:xs) = x ++ " (and " ++ show (length xs) ++ " more)"
  
  -- | Get the path to the current builder.
  getBuilderPath :: Builder b => b -> Expr c b FilePath
  getBuilderPath = expr . builderPath
  
  -- | Write a builder path into a given environment variable.
  builderEnvironment :: Builder b => String -> b -> Action CmdOption
  builderEnvironment variable builder = do
      needBuilder builder
      path <- builderPath builder
      return $ AddEnv variable path
  