module Rules.Lint
  ( lintRules
  ) where

import Base
import Settings.Builders.Common
import System.Directory (findExecutable)
import System.Exit (exitFailure)

lintRules :: Rules ()
lintRules = do
  "lint:base" ~> lint base
  "lint:compiler" ~> lint compiler

  -- Ensure that autoconf scripts, which are usually run by Cabal, are run to
  -- avoid depending upon Cabal from the stage0 compiler..
  "libraries" -/- "base" -/- "include" -/- "HsBaseConfig.h" %> \_ ->
      -- ./configure is called here manually because we need to generate
      -- HsBaseConfig.h, which is created from HsBaseConfig.h.in. ./configure
      cmd_ (Cwd "libraries/base") "./configure"
  "rts" -/- "include" -/- "ghcautoconf.h" %> \_ ->
      cmd_ (Cwd "rts") "./configure"

lint :: Action () -> Action ()
lint lintAction = do
  isHlintPresent <- isJust <$> liftIO (findExecutable "hlint")
  if isHlintPresent
  then do
    putBuild "| Running the linter…"
    lintAction
    putSuccess "| Done."
  else do
    putFailure "| Please make sure you have the `hlint` executable in your $PATH"
    liftIO exitFailure

runHLint :: [FilePath] -- ^ include directories
         -> [String]   -- ^ CPP defines
         -> FilePath
         -> Action ()
runHLint includeDirs defines dir = do
  threads <- shakeThreads <$> getShakeOptions
  hostArch <- (<> "_HOST_ARCH") <$> setting HostArch
  let hlintYaml = dir </> ".hlint.yaml"
      defines' = hostArch : defines
      cmdLine = unwords $
        [ "hlint"
        , "--colour=never"
        , "-j" <> show threads
        ] ++
        map ("--cpp-define=" <>) defines' ++
        map ("--cpp-include=" <>) includeDirs ++
        [ "-h" <> hlintYaml
        , dir
        ]
  putBuild $ "| " <> cmdLine
  cmd_ cmdLine

base :: Action ()
base = do
  buildDir <- buildRoot
  let stage1RtsInc = buildDir </> "stage1/rts/build/include"
  let machDeps     = "rts/include/MachDeps.h"
  let ghcautoconf  = stage1RtsInc </> "ghcautoconf.h"
  let ghcplatform  = stage1RtsInc </> "ghcplatform.h"
  need [ghcautoconf, ghcplatform, machDeps, "libraries/base/include/HsBaseConfig.h"]
  let includeDirs =
        [ "rts/include"
        , "libraries/base/include"
        , stage1RtsInc
        ]
  runHLint includeDirs [] "libraries/base"

compiler :: Action ()
compiler = do
  buildDir <- buildRoot
  let stage1RtsInc   = buildDir </> "stage1/rts/build/include"
  let stage1Compiler = buildDir </> "stage1/compiler/build"
  let machDeps       = "rts/include/MachDeps.h"
  let compilerDir    = "compiler"
  let ghcautoconf    = stage1RtsInc </> "ghcautoconf.h"
  let ghcplatform    = stage1RtsInc </> "ghcplatform.h"
  need $ mconcat [[ghcautoconf, ghcplatform], hsIncls stage1Compiler, [machDeps]]
  let includeDirs =
        [ stage1RtsInc
        , compilerDir
        , ghcplatform
        , stage1Compiler
        ]
  runHLint includeDirs [] "compiler"

hsIncls :: FilePath -> [FilePath]
hsIncls path = [ path </> "primop-vector-tycons.hs-incl"
               , path </> "primop-vector-tys.hs-incl"
               , path </> "primop-vector-tys-exports.hs-incl"
               , path </> "primop-code-size.hs-incl"
               , path </> "primop-vector-uniques.hs-incl"
               , path </> "primop-data-decl.hs-incl"
               , path </> "primop-tag.hs-incl"
               , path </> "primop-list.hs-incl"
               , path </> "primop-strictness.hs-incl"
               , path </> "primop-fixity.hs-incl"
               , path </> "primop-docs.hs-incl"
               , path </> "primop-primop-info.hs-incl"
               , path </> "primop-out-of-line.hs-incl"
               , path </> "primop-has-side-effects.hs-incl"
               , path </> "primop-can-fail.hs-incl"
               , path </> "primop-commutable.hs-incl"
               ]
