Skip to content

Commit f076bda

Browse files
committed
Add script support to cabal repl
repl starts in the correct directory and points directly to rather than a dummy, so that reloading works properly. There is a downside to the current approach which is that it uses a different fake-project.cabal file from run and build, so it cannot share the same cache with them. WIP: haskell#7842 WIP: haskell#6149
1 parent 76b4f06 commit f076bda

File tree

2 files changed

+84
-21
lines changed

2 files changed

+84
-21
lines changed

cabal-install/src/Distribution/Client/CmdRepl.hs

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ import Distribution.Client.ProjectPlanning
4646
( ElaboratedSharedConfig(..), ElaboratedInstallPlan )
4747
import Distribution.Client.ProjectPlanning.Types
4848
( elabOrderExeDependencies )
49+
import Distribution.Client.ScriptUtils
50+
( getScriptCacheDirectory, isLiterate, readScriptBlockFromScript )
4951
import Distribution.Client.Setup
5052
( GlobalFlags, ConfigFlags(..) )
5153
import qualified Distribution.Client.Setup as Client
@@ -85,6 +87,8 @@ import Distribution.Types.PackageDescription
8587
( PackageDescription(..), emptyPackageDescription )
8688
import Distribution.Types.PackageName.Magic
8789
( fakePackageId )
90+
import Distribution.Types.Executable
91+
( Executable(..), emptyExecutable )
8892
import Distribution.Types.Library
8993
( Library(..), emptyLibrary )
9094
import Distribution.Types.Version
@@ -93,6 +97,8 @@ import Distribution.Types.VersionRange
9397
( anyVersion )
9498
import Distribution.Utils.Generic
9599
( safeHead )
100+
import Distribution.Utils.Path
101+
( unsafeMakeSymbolicPath )
96102
import Distribution.Verbosity
97103
( normal, lessVerbose )
98104
import Distribution.Simple.Utils
@@ -102,14 +108,18 @@ import Language.Haskell.Extension
102108
import Distribution.CabalSpecVersion
103109
( CabalSpecVersion (..) )
104110

111+
import Control.Monad
112+
( (<=<) )
113+
import qualified Data.ByteString.Char8 as BS
105114
import Data.List
106115
( (\\) )
107116
import qualified Data.Map as Map
108117
import qualified Data.Set as Set
109118
import System.Directory
110-
( getCurrentDirectory, getTemporaryDirectory, removeDirectoryRecursive )
119+
( getCurrentDirectory, getTemporaryDirectory, removeDirectoryRecursive
120+
, doesFileExist, canonicalizePath)
111121
import System.FilePath
112-
( (</>) )
122+
( (</>), joinPath, splitPath, pathSeparator, takeFileName )
113123

114124
data EnvFlags = EnvFlags
115125
{ envPackages :: [Dependency]
@@ -345,43 +355,68 @@ withProject cliConfig verbosity targetStrings = do
345355
withoutProject :: ProjectConfig -> Verbosity -> [String]
346356
-> IO (ProjectBaseContext, [TargetSelector], IO (), ReplType)
347357
withoutProject config verbosity extraArgs = do
348-
unless (null extraArgs) $
349-
die' verbosity $ "'repl' doesn't take any extra arguments when outside a project: " ++ unwords extraArgs
358+
maybeScript <- case extraArgs of
359+
[] -> return Nothing
360+
[script] -> do
361+
exists <- doesFileExist script
362+
if exists
363+
then return $ Just script
364+
else die' verbosity $ "'repl' argument is not an script file: " ++ unwords extraArgs
365+
_ -> die' verbosity $ "'repl' takes a single script argument: " ++ unwords extraArgs
350366

351-
globalTmp <- getTemporaryDirectory
352-
tempDir <- createTempDirectory globalTmp "cabal-repl."
367+
368+
let
369+
mkTmpDir = do
370+
globalTmp <- getTemporaryDirectory
371+
createTempDirectory globalTmp "cabal-repl."
372+
readExec script =
373+
fmap fst . readScriptBlockFromScript verbosity (isLiterate script) =<< BS.readFile script
374+
375+
dir <- maybe mkTmpDir (getScriptCacheDirectory . ("repl:" ++)) maybeScript
376+
scriptExecutable <- maybe (return emptyExecutable) readExec maybeScript
377+
-- For scripts, we want to use cwd in hs-source-dirs, but hs-source-dirs wants a relative path
378+
backtocwd <- relativePathBackToCurrentDirectory dir
353379

354380
-- We need to create a dummy package that lives in our dummy project.
355381
let
356382
sourcePackage = SourcePackage
357383
{ srcpkgPackageId = pkgId
358384
, srcpkgDescription = genericPackageDescription
359-
, srcpkgSource = LocalUnpackedPackage tempDir
385+
, srcpkgSource = LocalUnpackedPackage dir
360386
, srcpkgDescrOverride = Nothing
361387
}
362388
genericPackageDescription = emptyGenericPackageDescription
363389
& L.packageDescription .~ packageDescription
364-
& L.condLibrary .~ Just (CondNode library [baseDep] [])
390+
& ( if isNothing maybeScript
391+
then L.condLibrary .~ Just (CondNode library [baseDep] [])
392+
else L.condExecutables .~ [("script", CondNode executable (targetBuildDepends eBuildInfo) [])] )
365393
packageDescription = emptyPackageDescription
366394
{ package = pkgId
367395
, specVersion = CabalSpecV2_2
368396
, licenseRaw = Left SPDX.NONE
369397
}
370-
library = emptyLibrary { libBuildInfo = buildInfo }
371-
buildInfo = emptyBuildInfo
398+
pkgId = fakePackageId
399+
400+
library = emptyLibrary { libBuildInfo = lBuildInfo }
401+
lBuildInfo = emptyBuildInfo
372402
{ targetBuildDepends = [baseDep]
373403
, defaultLanguage = Just Haskell2010
374404
}
375405
baseDep = Dependency "base" anyVersion mainLibSet
376-
pkgId = fakePackageId
377406

378-
writeGenericPackageDescription (tempDir </> "fake-package.cabal") genericPackageDescription
379-
380-
let ghciScriptPath = tempDir </> "setcwd.ghci"
381-
cwd <- getCurrentDirectory
382-
writeFile ghciScriptPath (":cd " ++ cwd)
407+
executable = scriptExecutable
408+
{ modulePath = maybe "" takeFileName maybeScript
409+
, buildInfo = eBuildInfo
410+
{ defaultLanguage =
411+
case defaultLanguage eBuildInfo of
412+
just@(Just _) -> just
413+
Nothing -> Just Haskell2010
414+
, hsSourceDirs = [unsafeMakeSymbolicPath backtocwd]
415+
}
416+
}
417+
eBuildInfo = buildInfo scriptExecutable
383418

384-
distDirLayout <- establishDummyDistDirLayout verbosity config tempDir
419+
distDirLayout <- establishDummyDistDirLayout verbosity config dir
385420
baseCtx <-
386421
establishDummyProjectBaseContext
387422
verbosity
@@ -390,12 +425,26 @@ withoutProject config verbosity extraArgs = do
390425
[SpecificSourcePackage sourcePackage]
391426
OtherCommand
392427

428+
writeGenericPackageDescription (dir </> "fake-package.cabal") genericPackageDescription
429+
maybe (return ()) (writeFile (dir </> "scriptlocation") <=< canonicalizePath) maybeScript
430+
431+
let ghciScriptPath = dir </> "setcwd.ghci"
432+
cwd <- getCurrentDirectory
433+
writeFile ghciScriptPath (":cd " ++ cwd)
434+
393435
let
394436
targetSelectors = [TargetPackage TargetExplicitNamed [pkgId] Nothing]
395-
finalizer = handleDoesNotExist () (removeDirectoryRecursive tempDir)
437+
finalizer | isNothing maybeScript = handleDoesNotExist () (removeDirectoryRecursive dir)
438+
| otherwise = return ()
396439

397440
return (baseCtx, targetSelectors, finalizer, GlobalRepl ghciScriptPath)
398441

442+
relativePathBackToCurrentDirectory :: FilePath -> IO FilePath
443+
relativePathBackToCurrentDirectory d = do
444+
toRoot <- joinPath . map (const "..") . splitPath . dropWhile (== pathSeparator) <$> canonicalizePath d
445+
cwd <- dropWhile (== pathSeparator) <$> getCurrentDirectory
446+
return $ toRoot </> cwd
447+
399448
addDepsToProjectTarget :: [Dependency]
400449
-> PackageId
401450
-> ProjectBaseContext

cabal-install/src/Distribution/Client/ScriptUtils.hs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
module Distribution.Client.ScriptUtils (
99
getScriptCacheDirectoryRoot, getScriptCacheDirectory,
1010
withTempTempDirectory,
11-
getContextAndSelectorsWithScripts
11+
getContextAndSelectorsWithScripts,
12+
isLiterate, readScriptBlockFromScript
1213
) where
1314

1415
import Prelude ()
@@ -123,8 +124,7 @@ getContextAndSelectorsWithScripts flags@NixStyleFlags {..} targetStrings globalF
123124
let
124125
scriptOrError script err = do
125126
exists <- doesFileExist script
126-
let pol | takeExtension script == ".lhs" = LiterateHaskell
127-
| otherwise = PlainHaskell
127+
let pol = isLiterate script
128128
if exists
129129
then do
130130
cacheDir <- getScriptCacheDirectory script
@@ -165,6 +165,14 @@ parseScriptBlock str =
165165
readScriptBlock :: Verbosity -> BS.ByteString -> IO Executable
166166
readScriptBlock verbosity = parseString parseScriptBlock verbosity "script block"
167167

168+
-- | Extract the first encountered script metadata block started end
169+
-- terminated by the bellow tokens or die.
170+
--
171+
-- * @{- cabal:@
172+
--
173+
-- * @-}@
174+
--
175+
-- Return the metadata and the contents of the file without the #! line.
168176
readScriptBlockFromScript :: Verbosity -> PlainOrLiterate -> BS.ByteString -> IO (Executable, BS.ByteString)
169177
readScriptBlockFromScript verbosity pol str = do
170178
str' <- case extractScriptBlock pol str of
@@ -213,6 +221,12 @@ data PlainOrLiterate
213221
= PlainHaskell
214222
| LiterateHaskell
215223

224+
-- | Test if a filepath is for a literate Haskell file.
225+
--
226+
isLiterate :: FilePath -> PlainOrLiterate
227+
isLiterate p | takeExtension p == ".lhs" = LiterateHaskell
228+
| otherwise = PlainHaskell
229+
216230
handleScriptCase
217231
:: Verbosity
218232
-> PlainOrLiterate

0 commit comments

Comments
 (0)