packages feed

zeolite-lang-0.24.0.0: src/Cli/RunCompiler.hs

{- -----------------------------------------------------------------------------
Copyright 2020-2021,2023 Kevin P. Barry

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
----------------------------------------------------------------------------- -}

-- Author: Kevin P. Barry [ta0kira@gmail.com]

module Cli.RunCompiler (
  TraceEntry(..),
  parseTracesFile,
  runCompiler,
) where

import Control.Monad (foldM,when)
import Data.List (intercalate,isSuffixOf,nub)
import System.Directory
import System.FilePath
import System.IO
import System.Posix.Temp (mkdtemp)
import qualified Data.Map as Map
import qualified Data.Set as Set

import Base.CompilerError
import Base.TrackedErrors
import Cli.CompileOptions
import Cli.Compiler
import Cli.Programs
import Module.CompileMetadata
import Module.Paths
import Module.ProcessMetadata
import Parser.Common
import Parser.TextParser


runCompiler :: (PathIOHandler r, CompilerBackend b) => r -> b -> CompileOptions -> TrackedErrorsIO ()
runCompiler resolver backend (CompileOptions _ _ _ ds _ _ p (ExecuteTests tp cl) f _) = do
  base <- resolveBaseModule resolver
  ts <- fmap snd $ foldM preloadTests (Map.empty,[]) ds
  checkTestFilters ts
  cl2 <- prepareCallLog cl
  allResults <- fmap concat $ mapCompilerM (runModuleTests resolver backend cl2 base tp) ts
  let passed = sum $ map (fst . fst) allResults
  let failed = sum $ map (snd . fst) allResults
  processResults passed failed (mapCompilerM_ snd allResults) where
    prepareCallLog (Just cl2) = do
      clFull <- errorFromIO $ canonicalizePath (p </> cl2)
      compilerWarningM $ "Logging calls to " ++ clFull
      errorFromIO $ writeFile clFull (intercalate "," (map show tracesLogHeader) ++ "\n")
      return clFull
    prepareCallLog _ = return ""
    preloadTests (ca,ms) d = do
      compilerHash <- getCompilerHash backend
      m <- loadModuleMetadata compilerHash f ca (p </> d)
      let ca2 = ca `Map.union` mapMetadata [m]
      rm <- loadRecompile (p </> d)
      let ca3 = ca2 `Map.union` mapMetadata []
      deps1 <- loadTestingDeps compilerHash f ca3 m
      let ca4 = ca3 `Map.union` mapMetadata deps1
      deps2 <- loadPrivateDeps compilerHash f ca4 ([m]++deps1)
      let ca5 = ca4 `Map.union` mapMetadata deps2
      em <- getExprMap (p </> d) rm
      return (ca5,ms ++ [LoadedTests m em (deps1) deps2])
    checkTestFilters ts = do
      let possibleTests = concat $ map (cmTestFiles . ltMetadata) ts
      let remaining = filter (not . flip any (map (flip isSuffixOf) possibleTests). flip ($)) tp
      case remaining of
          [] -> return ()
          fs -> compilerErrorM $ "Some test files do not occur in the selected modules: " ++
                                intercalate ", " (map show fs) ++ "\n"
    processResults passed failed rs
      | isCompilerError rs =
        (fromTrackedErrors rs) <!!
          "\nPassed: " ++ show passed ++ " test(s), Failed: " ++ show failed ++ " test(s)"
      | otherwise =
        errorFromIO $ hPutStrLn stderr $ "\nPassed: " ++ show passed ++ " test(s), Failed: " ++ show failed ++ " test(s)"

runCompiler resolver backend (CompileOptions _ is is2 _ _ _ p (CompileFast c fn f2) f pn) = do
  dir <- errorFromIO $ mkdtemp "/tmp/zfast_"
  absolute <- errorFromIO $ canonicalizePath p
  f2' <- errorFromIO $ canonicalizePath (p </> f2)
  let rm = ModuleConfig {
    mcRoot = "",
    mcPath = ".",
    mcExtra = [],
    mcExprMap = [],
    mcPublicDeps = [],
    mcPrivateDeps = [],
    mcExtraFiles = [],
    mcCategories = [],
    mcExtraPaths = [],
    mcMode = CompileUnspecified
  }
  em <- getExprMap p rm
  let spec = ModuleSpec {
    msRoot = absolute,
    msPath = dir,
    msExtra = [],
    msExprMap = em,
    msPublicDeps = is,
    msPrivateDeps = is2,
    msPublicFiles = [],
    msPrivateFiles = [f2'],
    msTestFiles = [],
    msExtraFiles = [],
    msCategories = [],
    msExtraPaths = [],
    msMode = (CompileBinary c fn LinkStatic (absolute </> show c) []),
    msForce = f,
    msParallel = pn
  }
  compileModule resolver backend spec <?? "In compilation of \"" ++ f2' ++ "\""
  errorFromIO $ removeDirectoryRecursive dir

runCompiler resolver backend (CompileOptions _ _ _ ds _ _ p CompileRecompileRecursive f pn) =
  runRecompileCommon resolver backend f pn True p ds

runCompiler resolver backend (CompileOptions _ _ _ ds _ _ p CompileRecompile f pn) =
  runRecompileCommon resolver backend f pn False p ds

runCompiler resolver backend (CompileOptions _ is is2 ds _ _ p CreateTemplates f _) = mapM_ compileSingle ds where
  compileSingle d = do
    compilerHash <- getCompilerHash backend
    d' <- errorFromIO $ canonicalizePath (p </> d)
    (ep,is',is2',cm) <- maybeUseConfig d'
    as  <- fmap fixPaths $ mapCompilerM (resolveModule resolver d') is'
    as2 <- fmap fixPaths $ mapCompilerM (resolveModule resolver d') is2'
    isBase <- isBaseModule resolver d'
    deps1 <- if isBase
                then loadPublicDeps compilerHash f Map.empty as
                else do
                  base <- resolveBaseModule resolver
                  loadPublicDeps compilerHash f Map.empty (base:as)
    deps2 <- loadPublicDeps compilerHash f (mapMetadata deps1) as2
    path <- errorFromIO $ canonicalizePath p
    createModuleTemplates resolver path d ep cm deps1 deps2 <?? "In module \"" ++ d' ++ "\""
  maybeUseConfig d2 = do
    let rm = loadRecompile d2
    isError <- isCompilerErrorM rm
    if isError
       then return ([],is,is2,Map.empty)
       else do
         (ModuleConfig p2 _ ep _ is3 is4  _ cs _ _) <- rm
         return (map ((d2 </> p2) </>) ep,nub $ is ++ is3,nub $ is2 ++ is4,Map.fromList cs)

runCompiler resolver _ (CompileOptions _ is is2 ds es ep p m f _) = mapM_ compileSingle ds where
  compileSingle d = do
    as  <- fmap fixPaths $ mapCompilerM (autoDep (p </> d)) is
    as2 <- fmap fixPaths $ mapCompilerM (autoDep (p </> d)) is2
    isConfigured <- isPathConfigured p d
    when (isConfigured && f == DoNotForce) $ do
      compilerErrorM $ "Module " ++ d ++ " has an existing configuration. " ++
                       "Recompile with -r or use -f to overwrite the config."
    absolute <- errorFromIO $ canonicalizePath p
    let rm = ModuleConfig {
      mcRoot = absolute,
      mcPath = d,
      mcExtra = [],
      mcExprMap = [],
      mcPublicDeps = as,
      mcPrivateDeps = as2,
      mcExtraFiles = es,
      mcCategories = [],
      mcExtraPaths = ep,
      mcMode = m
    }
    writeRecompile (p </> d) rm
    config <- getRecompilePath (p </> d)
    errorFromIO $ hPutStrLn stderr $ "*** Setup complete. Please edit " ++ config ++ " and recompile with zeolite -r. ***"
  autoDep p2 i = do
    isSystem <- isSystemModule resolver p2 i
    if isSystem
       then return i
       else resolveModule resolver p2 i

data TraceEntry =
  TraceEntry {
    teMicroseconds :: Integer,
    teProcess :: Integer,
    teFunction :: String,
    teContext :: String
  }

tracesLogHeader :: [String]
tracesLogHeader = ["microseconds","pid","function","context"]

parseTracesFile :: (FilePath,String) -> TrackedErrorsIO [TraceEntry]
parseTracesFile (f,s) = runTextParser (between nullParse endOfDoc tracesFile) f s where
  tracesFile =  do
    parseHeader
    many parseSingle
  parseHeader = do
    sequence_ $ intercalate [string_ ","] $ map ((:[]) . parseColTitle) tracesLogHeader
    some (char '\n' <|> char '\r') >> return ()
  parseSingle = do
    ms <- fmap snd parseDec
    string_ ","
    pid <- fmap snd parseDec
    string_ ","
    func <- quotedString
    string_ ","
    c <- quotedString
    some (char '\n' <|> char '\r') >> return ()
    return $ TraceEntry ms pid func c
  parseColTitle expected = do
    title <- quotedString
    when (expected /= title) $ compilerErrorM $ "Expected column named \"" ++ expected ++ "\" but found \"" ++ title ++ "\""

runRecompileCommon :: (PathIOHandler r, CompilerBackend b) => r -> b ->
  ForceMode -> Int -> Bool -> FilePath -> [FilePath] -> TrackedErrorsIO ()
runRecompileCommon resolver backend f pn rec p ds = do
  explicit <- fmap Set.fromList $ mapCompilerM (errorFromIO . canonicalizePath . (p </>)) ds
  foldM (recursive resolver explicit) Set.empty (map ((,) p) ds) >> return () where
    recursive r explicit da (p2,d0) = do
      d <- resolveModule r p2 d0
      isSystem <- isSystemModule r p2 d0
      let process = if rec
                       then d `Set.member` explicit || not isSystem
                       else d `Set.member` explicit
      if not process || d `Set.member` da
         then return da
         else do
           rm <- loadRecompile d
           let ds3 = map ((,) d) (mcPublicDeps rm ++ mcPrivateDeps rm)
           da' <- foldM (recursive r explicit) (d `Set.insert` da) ds3
           recompile d
           return da'
    recompile d = do
      compilerHash <- getCompilerHash backend
      upToDate <- isPathUpToDate compilerHash f d
      if f < ForceAll && upToDate
         then compilerWarningM $ "Path " ++ d ++ " is up to date"
         else do
           rm@(ModuleConfig p2 d2 ee _ is is2 es cs ep m) <- loadRecompile d
           let fixed = fixPath (d </> p2)
           (ps,xs,ts) <- findSourceFiles fixed (d2:ee)
           em <- getExprMap d rm
           let spec = ModuleSpec {
             msRoot = fixed,
             msPath = d2,
             msExtra = ee,
             msExprMap = em,
             msPublicDeps = is,
             msPrivateDeps = is2,
             msPublicFiles = ps,
             msPrivateFiles = xs,
             msTestFiles = ts,
             msExtraFiles = es,
             msCategories = cs,
             msExtraPaths = ep,
             msMode = m,
             msForce = f,
             msParallel = pn
           }
           compileModule resolver backend spec <?? "In compilation of module \"" ++ d ++ "\""