<commit_before>-- cabal-helper: Simple interface to Cabal's configuration state
-- Copyright (C) 2015-2018  <NAME> <<EMAIL>>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

{-# LANGUAGE DeriveFunctor, GADTs, ScopedTypeVariables #-}

{-|
Module      : CabalHelper.Compiletime.Compile
Description : Runtime compilation machinery
License     : GPL-3
-}

module CabalHelper.Compiletime.Compile where

import qualified Cabal.Plan as CP
import Cabal.Plan
  ( PlanJson(..), PkgId(..), PkgName(..), Ver(..), uPId)
import Control.Applicative
import Control.Arrow
import Control.Exception as E
import Control.Monad
import Control.Monad.Trans.Maybe
import Control.Monad.IO.Class
import Data.Char
import Data.List
import Data.Maybe
import Data.String
import Data.Version
import Text.Printf
import qualified System.Clock as Clock
import System.Directory
import System.FilePath
import System.Exit
import System.IO
import System.IO.Temp
import Prelude

import qualified Data.Text as Text
import qualified Data.Map.Strict as Map

import Distribution.System
  ( buildPlatform )
import Distribution.Text
  ( display )

import CabalHelper.Compiletime.Cabal
import CabalHelper.Compiletime.Data
import CabalHelper.Compiletime.Log
import CabalHelper.Compiletime.Program.GHC
import CabalHelper.Compiletime.Program.CabalInstall
import CabalHelper.Compiletime.Sandbox
    ( getSandboxPkgDb )
import CabalHelper.Compiletime.Types

import CabalHelper.Shared.Common

import Paths_cabal_helper (version)


data Compile
    = CompileWithCabalSource
      { compCabalSourceDir     :: !CabalSourceDir
      , compCabalSourceVersion :: !Version
      }
    | CompileWithCabalPackage
      { compPackageSource  :: !GhcPackageSource
      , compCabalVersion   :: !ResolvedCabalVersion
      , compProductTarget  :: !CompilationProductScope
      }

data CompPaths = CompPaths
    { compBuildDir:: !FilePath
    , compOutDir  :: !FilePath
    , compExePath :: !FilePath
    }

-- | The Helper executable we produce as a compilation product can either be
-- placed in a per-project location, or a per-user/global location in the user's
-- home directory. This type controls where the compilation process places the
-- executable.
data CompilationProductScope = CPSGlobal | CPSProject

type CompHelperEnv = CompHelperEnv' CabalVersion
data CompHelperEnv' cv = CompHelperEnv
  { cheCabalVer :: !cv
  , chePkgDb    :: !(Maybe PackageDbDir)
  -- ^ A package-db where we are guaranteed to find Cabal-`cheCabalVer`.
  , cheProjDir  :: !FilePath
  , chePlanJson :: !(Maybe PlanJson)
  , cheDistV2   :: !(Maybe FilePath)
  , cheProjLocalCacheDir :: FilePath
  }

compileHelper
    :: Env => CompHelperEnv -> IO (Either ExitCode FilePath)
compileHelper che@CompHelperEnv {cheCabalVer} = do
  withSystemTempDirectory "cabal-helper.compile-tmp" $ \tmpdir -> do
    ucv <- unpackCabal cheCabalVer tmpdir
    compileHelper' che { cheCabalVer = ucv }

compileHelper'
    :: Env
    => CompHelperEnv' UnpackedCabalVersion
    -> IO (Either ExitCode FilePath)
compileHelper' CompHelperEnv {..} = do
  t0 <- Clock.getTime Clock.Monotonic
  ghcVer <- ghcVersion
  Just (prepare, comp) <- case cheCabalVer of
    cabalVer@CabalHEAD {} -> runMaybeT $ msum  $ map (\f -> f ghcVer cabalVer)
      [ compileWithCabalV2GhcEnv'
      , compileWithCabalInPrivatePkgDb'
      ]
    CabalVersion cabalVerPlain -> do
      runMaybeT $ msum $ map (\f -> f ghcVer cabalVerPlain) $
        case chePkgDb of
          Nothing ->
            [ compileWithCabalV2Inplace
            , compileWithCabalV2GhcEnv
            , compileCabalSource
            , compileSandbox
            , compileGlobal
            , compileWithCabalInPrivatePkgDb
            ]
          Just db ->
            [ ((.).(.)) liftIO (compilePkgDb db)
            ]
  appdir <- appCacheDir
  let cp@CompPaths {compExePath} = compPaths appdir cheProjLocalCacheDir comp
  helper_exists <- doesFileExist compExePath
  rv <- if helper_exists
    then do
      vLog $ "helper already compiled, using exe: "++compExePath
      return (Right compExePath)
    else do
      vLog $ "helper exe does not exist, compiling "++compExePath
      prepare >> compile cp comp

  t1 <- Clock.getTime Clock.Monotonic
  let dt = (/10e9) $ fromInteger $ Clock.toNanoSecs $ Clock.diffTimeSpec t0 t1
      dt :: Float
  vLog $ printf "compileHelper took %.5fs" dt
  return rv


  where
   logMsg = "using helper compiled with Cabal from "

-- for relaxed deps: find (sameMajorVersionAs cheCabalVer) . reverse . sort

   compilePkgDb db _ghcVer cabalVer  = return $
       (,)
         (pure ())
         CompileWithCabalPackage
           { compPackageSource = GPSPackageDBs [db]
           , compCabalVersion  = CabalVersion cabalVer
           , compProductTarget = CPSProject
           }

   -- | Check if this version is globally available<commit_msg>Remove redundant timing code for compileHelper<commit_after>-- cabal-helper: Simple interface to Cabal's configuration state
-- Copyright (C) 2015-2018  <NAME> <<EMAIL>>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

{-# LANGUAGE DeriveFunctor, GADTs, ScopedTypeVariables #-}

{-|
Module      : CabalHelper.Compiletime.Compile
Description : Runtime compilation machinery
License     : GPL-3
-}

module CabalHelper.Compiletime.Compile where

import qualified Cabal.Plan as CP
import Cabal.Plan
  ( PlanJson(..), PkgId(..), PkgName(..), Ver(..), uPId)
import Control.Applicative
import Control.Arrow
import Control.Exception as E
import Control.Monad
import Control.Monad.Trans.Maybe
import Control.Monad.IO.Class
import Data.Char
import Data.List
import Data.Maybe
import Data.String
import Data.Version
import Text.Printf
import qualified System.Clock as Clock
import System.Directory
import System.FilePath
import System.Exit
import System.IO
import System.IO.Temp
import Prelude

import qualified Data.Text as Text
import qualified Data.Map.Strict as Map

import Distribution.System
  ( buildPlatform )
import Distribution.Text
  ( display )

import CabalHelper.Compiletime.Cabal
import CabalHelper.Compiletime.Data
import CabalHelper.Compiletime.Log
import CabalHelper.Compiletime.Program.GHC
import CabalHelper.Compiletime.Program.CabalInstall
import CabalHelper.Compiletime.Sandbox
    ( getSandboxPkgDb )
import CabalHelper.Compiletime.Types

import CabalHelper.Shared.Common

import Paths_cabal_helper (version)


data Compile
    = CompileWithCabalSource
      { compCabalSourceDir     :: !CabalSourceDir
      , compCabalSourceVersion :: !Version
      }
    | CompileWithCabalPackage
      { compPackageSource  :: !GhcPackageSource
      , compCabalVersion   :: !ResolvedCabalVersion
      , compProductTarget  :: !CompilationProductScope
      }

data CompPaths = CompPaths
    { compBuildDir:: !FilePath
    , compOutDir  :: !FilePath
    , compExePath :: !FilePath
    }

-- | The Helper executable we produce as a compilation product can either be
-- placed in a per-project location, or a per-user/global location in the user's
-- home directory. This type controls where the compilation process places the
-- executable.
data CompilationProductScope = CPSGlobal | CPSProject

type CompHelperEnv = CompHelperEnv' CabalVersion
data CompHelperEnv' cv = CompHelperEnv
  { cheCabalVer :: !cv
  , chePkgDb    :: !(Maybe PackageDbDir)
  -- ^ A package-db where we are guaranteed to find Cabal-`cheCabalVer`.
  , cheProjDir  :: !FilePath
  , chePlanJson :: !(Maybe PlanJson)
  , cheDistV2   :: !(Maybe FilePath)
  , cheProjLocalCacheDir :: FilePath
  }

compileHelper
    :: Env => CompHelperEnv -> IO (Either ExitCode FilePath)
compileHelper che@CompHelperEnv {cheCabalVer} = do
  withSystemTempDirectory "cabal-helper.compile-tmp" $ \tmpdir -> do
    ucv <- unpackCabal cheCabalVer tmpdir
    compileHelper' che { cheCabalVer = ucv }

compileHelper'
    :: Env
    => CompHelperEnv' UnpackedCabalVersion
    -> IO (Either ExitCode FilePath)
compileHelper' CompHelperEnv {..} = do
  ghcVer <- ghcVersion
  Just (prepare, comp) <- case cheCabalVer of
    cabalVer@CabalHEAD {} -> runMaybeT $ msum  $ map (\f -> f ghcVer cabalVer)
      [ compileWithCabalV2GhcEnv'
      , compileWithCabalInPrivatePkgDb'
      ]
    CabalVersion cabalVerPlain -> do
      runMaybeT $ msum $ map (\f -> f ghcVer cabalVerPlain) $
        case chePkgDb of
          Nothing ->
            [ compileWithCabalV2Inplace
            , compileWithCabalV2GhcEnv
            , compileCabalSource
            , compileSandbox
            , compileGlobal
            , compileWithCabalInPrivatePkgDb
            ]
          Just db ->
            [ ((.).(.)) liftIO (compilePkgDb db)
            ]
  appdir <- appCacheDir
  let cp@CompPaths {compExePath} = compPaths appdir cheProjLocalCacheDir comp
  helper_exists <- doesFileExist compExePath
  rv <- if helper_exists
    then do
      vLog $ "helper already compiled, using exe: "++compExePath
      return (Right compExePath)
    else do
      vLog $ "helper exe does not exist, compiling "++compExePath
      prepare >> compile cp comp
  return rv


  where
   logMsg = "using helper compiled with Cabal from "

-- for relaxed deps: find (sameMajorVersionAs cheCabalVer) . reverse . sort

   compilePkgDb db _ghcVer cabalVer  = return $
       (,)
         (pure ())
         CompileWithCabalPackage
           { compPackageSource = GPSPackageDBs [db]
           , compCabalVersion  = CabalVersion cabalVer
           , compProductTarget = CPSProject
           }

   -- | Check if this version is globally available
{'fraction_non_alphanumeric': 0.08154867670438605, 'fraction_numerical': 0.0033666884877957544, 'mean_word_length': 3.2352475247524755, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1995647', 'n_tokens_mistral': 3267, 'n_tokens_neox': 3157, 'n_words': 968}