@@ -14,7 +14,8 @@ module Obelisk.Command.Run where
14import Control.Arrow ((&&&))
14import Control.Arrow ((&&&))
15import Control.Exception (Exception, bracket)
15import Control.Exception (Exception, bracket)
16import Control.Lens (ifor, (.~), (&))
16import Control.Lens (ifor, (.~), (&))
17−import Control.Monad (filterM)
17+import Control.Concurrent (forkIO)
18+import Control.Monad (filterM, void)
18import Control.Monad.Except (runExceptT, throwError)
19import Control.Monad.Except (runExceptT, throwError)
19import Control.Monad.IO.Class (liftIO)
20import Control.Monad.IO.Class (liftIO)
20import Control.Monad.Reader (MonadIO)
21import Control.Monad.Reader (MonadIO)
@@ -45,13 +46,23 @@ import Debug.Trace (trace)
45import Distribution.Compiler (CompilerFlavor(..))
46import Distribution.Compiler (CompilerFlavor(..))
46import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)
47import Distribution.PackageDescription.Parsec (parseGenericPackageDescription)
47import Distribution.Parsec.ParseResult (runParseResult)
48import Distribution.Parsec.ParseResult (runParseResult)
49+import Distribution.Pretty (prettyShow)
50+import Distribution.Simple.Compiler (PackageDB (GlobalPackageDB))
51+import Distribution.Simple.Configure (configCompilerEx, getInstalledPackages)
52+import Distribution.Simple.PackageIndex (InstalledPackageIndex, lookupDependency)
53+import Distribution.Simple.Program.Db (defaultProgramDb)
48import qualified Distribution.System as Dist
54import qualified Distribution.System as Dist
49−import Distribution.Types.BuildInfo (buildable, cppOptions, defaultExtensions, defaultLanguage, hsSourceDirs, options)
55+import Distribution.Types.BuildInfo (buildable, cppOptions, defaultExtensions, defaultLanguage, hsSourceDirs, options, targetBuildDepends)
50import Distribution.Types.CondTree (simplifyCondTree)
56import Distribution.Types.CondTree (simplifyCondTree)
57+import Distribution.Types.Dependency (Dependency (..), depPkgName)
51import Distribution.Types.GenericPackageDescription (ConfVar (Arch, Impl, OS), condLibrary)
58import Distribution.Types.GenericPackageDescription (ConfVar (Arch, Impl, OS), condLibrary)
59+import Distribution.Types.InstalledPackageInfo (compatPackageKey)
52import Distribution.Types.Library (libBuildInfo)
60import Distribution.Types.Library (libBuildInfo)
61+import Distribution.Types.PackageName (mkPackageName)
62+import Distribution.Types.VersionRange (anyVersion)
53import Distribution.Utils.Generic (toUTF8BS, readUTF8File)
63import Distribution.Utils.Generic (toUTF8BS, readUTF8File)
54import qualified Distribution.Parsec.Common as Dist
64import qualified Distribution.Parsec.Common as Dist
65+import qualified Distribution.Verbosity as Verbosity (silent)
55import qualified Hpack.Config as Hpack
66import qualified Hpack.Config as Hpack
56import qualified Hpack.Render as Hpack
67import qualified Hpack.Render as Hpack
57import qualified Hpack.Yaml as Hpack
68import qualified Hpack.Yaml as Hpack
@@ -63,7 +74,7 @@ import System.FilePath
63import qualified System.Info
74import qualified System.Info
64import System.IO.Temp (withSystemTempDirectory)
75import System.IO.Temp (withSystemTempDirectory)
66−import Obelisk.App (MonadObelisk)
77+import Obelisk.App (MonadObelisk, getObelisk, runObelisk)
67import Obelisk.CliApp (
78import Obelisk.CliApp (
68 Severity (..),
79 Severity (..),
@@ -77,7 +88,7 @@ import Obelisk.CliApp (
77 withSpinner,
88 withSpinner,
79import Obelisk.Command.Nix
90import Obelisk.Command.Nix
80−import Obelisk.Command.Project (nixShellWithoutPkgs, withProjectRoot, findProjectAssets, bashEscape)
91+import Obelisk.Command.Project
81import Obelisk.Command.Thunk (attrCacheFileName)
92import Obelisk.Command.Thunk (attrCacheFileName)
82import Obelisk.Command.Utils (findExePath, ghcidExePath)
93import Obelisk.Command.Utils (findExePath, ghcidExePath)
@@ -96,6 +107,8 @@ data CabalPackageInfo = CabalPackageInfo
96 -- ^ List of compiler-specific options (e.g., the "ghc-options" field of the cabal file)
107 -- ^ List of compiler-specific options (e.g., the "ghc-options" field of the cabal file)
97 , _cabalPackageInfo_cppOptions :: [String]
108 , _cabalPackageInfo_cppOptions :: [String]
98 -- ^ List of CPP (C Preprocessor) options (e.g. the "cpp-options" field of the cabal file)
109 -- ^ List of CPP (C Preprocessor) options (e.g. the "cpp-options" field of the cabal file)
110+ , _cabalPackageInfo_buildDepends :: [Dependency]
111+ -- ^ List of build dependencies listed in the cabal file
101-- | 'Bool' with a better name for its purpose.
114-- | 'Bool' with a better name for its purpose.
@@ -144,8 +157,8 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
144 , _target_attr = Just "__unstable__.profiledObRun"
157 , _target_attr = Just "__unstable__.profiledObRun"
145 , _target_expr = Nothing
158 , _target_expr = Nothing
147− assets <- findProjectAssets root
160+ (assetType, assets) <- findProjectAssets root
148− putLog Debug $ "Assets impurely loaded from: " <> assets
161+ putLog Debug $ describeImpureAssetSource assetType assets
149 time <- liftIO getCurrentTime
162 time <- liftIO getCurrentTime
150 let profileBaseName = formatTime defaultTimeLocale profileBasePattern time
163 let profileBaseName = formatTime defaultTimeLocale profileBasePattern time
151 liftIO $ createDirectoryIfMissing True $ takeDirectory $ root </> profileBaseName
164 liftIO $ createDirectoryIfMissing True $ takeDirectory $ root </> profileBaseName
@@ -163,9 +176,16 @@ profile profileBasePattern rtsFlags = withProjectRoot "." $ \root -> do
163run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
176run :: MonadObelisk m => FilePath -> PathTree Interpret -> m ()
164run root interpretPaths = do
177run root interpretPaths = do
165 pkgs <- getParsedLocalPkgs root interpretPaths
178 pkgs <- getParsedLocalPkgs root interpretPaths
166− assets <- findProjectAssets root
179+ (assetType, assets) <- findProjectAssets root
167− putLog Debug $ "Assets impurely loaded from: " <> assets
180+ manifestPkg <- parsePackagesOrFail . (:[]) . T.unpack =<< getHaskellManifestProjectPath root
181+ putLog Debug $ describeImpureAssetSource assetType assets
183+ AssetSource_Derivation -> do
185+ putLog Debug "Starting static file derivation watcher..."
186+ void $ liftIO $ forkIO $ runObelisk ob $ watchStaticFilesDerivation root
168− ghciArgs <- getGhciSessionSettings pkgs root True
188+ ghciArgs <- getGhciSessionSettings (pkgs <> manifestPkg) root True
169 freePort <- getFreePort
189 freePort <- getFreePort
170 withGhciScriptArgs pkgs $ \dotGhciArgs -> do
190 withGhciScriptArgs pkgs $ \dotGhciArgs -> do
171 runGhcid root True (ghciArgs <> dotGhciArgs) pkgs $ Just $ unwords
191 runGhcid root True (ghciArgs <> dotGhciArgs) pkgs $ Just $ unwords
@@ -222,7 +242,12 @@ getLocalPkgs root interpretPaths = do
222 let rootsAndExclusions = calcIntepretFinds "" interpretPaths
242 let rootsAndExclusions = calcIntepretFinds "" interpretPaths
224 fmap fold $ for (MMap.toAscList rootsAndExclusions) $ \(interpretPathRoot, exclusions) ->
244 fmap fold $ for (MMap.toAscList rootsAndExclusions) $ \(interpretPathRoot, exclusions) ->
225− let allExclusions = obeliskPackageExclusions <> exclusions <> Set.singleton ("*" </> attrCacheFileName)
245+ let allExclusions = obeliskPackageExclusions
247+ <> Set.singleton ("*" </> attrCacheFileName)
248+ <> Set.singleton ("*" </> "lib/asset/manifest") -- NB: obelisk-asset-manifest is excluded because it generates
249+ -- a module that in turn imports it. This will cause ob run to
250+ -- fail in its current implementation.
226 in fmap (Set.fromList . map normalise) $ runFind $
251 in fmap (Set.fromList . map normalise) $ runFind $
227 ["-L", interpretPathRoot, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
252 ["-L", interpretPathRoot, "(", "-name", "*.cabal", "-o", "-name", Hpack.packageConfig, ")", "-a", "-type", "f"]
228 <> concat [["-not", "-path", p </> "*"] | p <- toList allExclusions]
253 <> concat [["-not", "-path", p </> "*"] | p <- toList allExclusions]
@@ -354,6 +379,7 @@ parseCabalPackage' pkg = runExceptT $ do
354 defaultLanguage $ libBuildInfo lib
379 defaultLanguage $ libBuildInfo lib
355 , _cabalPackageInfo_compilerOptions = options $ libBuildInfo lib
380 , _cabalPackageInfo_compilerOptions = options $ libBuildInfo lib
356 , _cabalPackageInfo_cppOptions = cppOptions $ libBuildInfo lib
381 , _cabalPackageInfo_cppOptions = cppOptions $ libBuildInfo lib
382+ , _cabalPackageInfo_buildDepends = targetBuildDepends $ libBuildInfo lib
358 Right Nothing -> pure Nothing
384 Right Nothing -> pure Nothing
359 Left (_, errors) ->
385 Left (_, errors) ->
@@ -400,7 +426,8 @@ withGhciScriptArgs
400 => f CabalPackageInfo -- ^ List of packages to load into ghci
426 => f CabalPackageInfo -- ^ List of packages to load into ghci
401 -> ([String] -> m ()) -- ^ Action to run with the extra ghci arguments
427 -> ([String] -> m ()) -- ^ Action to run with the extra ghci arguments
403−withGhciScriptArgs packageInfos f = withGhciScript loadPreludeManually packageInfos $ \fp -> f ["-XNoImplicitPrelude", "-ghci-script", fp]
429+withGhciScriptArgs packageInfos f = withGhciScript loadPreludeManually packageInfos $ \fp ->
430+ f ["-XNoImplicitPrelude", "-ghci-script", fp]
405 -- These lines must be first and allow the session to support a custom Prelude when @-XNoImplicitPrelude@
432 -- These lines must be first and allow the session to support a custom Prelude when @-XNoImplicitPrelude@
406 -- is passed to the ghci session.
433 -- is passed to the ghci session.
@@ -450,6 +477,7 @@ getGhciSessionSettings (toList -> packageInfos) pathBase useRelativePaths = do
450 -- all paths to 'pathBase'.
477 -- all paths to 'pathBase'.
451 selfExe <- liftIO $ canonicalizePath =<< getExecutablePath
478 selfExe <- liftIO $ canonicalizePath =<< getExecutablePath
452 canonicalPathBase <- liftIO $ canonicalizePath pathBase
479 canonicalPathBase <- liftIO $ canonicalizePath pathBase
480+ installedPackageIndex <- loadPackageIndex packageInfos pathBase
454 (pkgFiles, pkgSrcPaths :: [NonEmpty FilePath]) <- fmap unzip $ liftIO $ for packageInfos $ \pkg -> do
482 (pkgFiles, pkgSrcPaths :: [NonEmpty FilePath]) <- fmap unzip $ liftIO $ for packageInfos $ \pkg -> do
455 canonicalSrcDirs <- traverse canonicalizePath $ (_cabalPackageInfo_packageRoot pkg </>) <$> _cabalPackageInfo_sourceDirs pkg
483 canonicalSrcDirs <- traverse canonicalizePath $ (_cabalPackageInfo_packageRoot pkg </>) <$> _cabalPackageInfo_sourceDirs pkg
@@ -458,16 +486,47 @@ getGhciSessionSettings (toList -> packageInfos) pathBase useRelativePaths = do
460 $ baseGhciOptions
488 $ baseGhciOptions
489+ <> ["-DOBELISK_ASSET_PASSTHRU"] -- For passthrough static assets
461 <> ["-F", "-pgmF", selfExe, "-optF", preprocessorIdentifier]
490 <> ["-F", "-pgmF", selfExe, "-optF", preprocessorIdentifier]
462 <> concatMap (\p -> ["-optF", p]) pkgFiles
491 <> concatMap (\p -> ["-optF", p]) pkgFiles
463 <> [ "-i" <> intercalate ":" (concatMap toList pkgSrcPaths) ]
492 <> [ "-i" <> intercalate ":" (concatMap toList pkgSrcPaths) ]
493+ <> concatMap (\packageId -> ["-package-id", packageId ])
494+ (packageIds installedPackageIndex)
465 relativeTo' = if useRelativePaths then relativeTo else const
496 relativeTo' = if useRelativePaths then relativeTo else const
497+ -- Package names we're building and not needed from the package DB
499+ map (mkPackageName . T.unpack . _cabalPackageInfo_packageName)
501+ packageIds installedPackageIndex =
502+ map (dependencyPackageId installedPackageIndex) $
503+ filter ((`notElem` packageNames) . depPkgName) $
504+ concatMap _cabalPackageInfo_buildDepends packageInfos <>
505+ [Dependency (mkPackageName "obelisk-run") anyVersion]
506+ dependencyPackageId installedPackageIndex dep =
507+ case lookupDependency installedPackageIndex dep of
508+ ((_version,installedPackageInfo:_) :_) ->
509+ compatPackageKey installedPackageInfo
510+ _ -> error $ "Couldn't resolve dependency for " <> prettyShow dep
512+-- Load the package index used by the GHC in this path's nix project
513+loadPackageIndex :: MonadObelisk m => [CabalPackageInfo] -> FilePath -> m InstalledPackageIndex
514+loadPackageIndex packageInfos root = do
515+ ghcPath <- getPathInNixEnvironment "bash -c 'type -p ghc'"
516+ ghcPkgPath <- getPathInNixEnvironment "bash -c 'type -p ghc-pkg'"
517+ (compiler, _platform, programDb) <- liftIO
518+ $ configCompilerEx (Just GHC) (Just ghcPath) (Just ghcPkgPath) defaultProgramDb Verbosity.silent
519+ liftIO $ getInstalledPackages Verbosity.silent compiler [GlobalPackageDB] programDb
521+ getPathInNixEnvironment cmd = do
522+ path <- readProcessAndLogStderr Debug =<< mkObNixShellProc root False True (packageInfoToNamePathMap packageInfos) "ghc" (Just cmd)
523+ liftIO $ canonicalizePath $ T.unpack $ T.strip path
467baseGhciOptions :: [String]
525baseGhciOptions :: [String]
468baseGhciOptions =
526baseGhciOptions =
469 [ "-ignore-dot-ghci"
527 [ "-ignore-dot-ghci"
470 , "-no-user-package-db"
528 , "-no-user-package-db"
529+ , "-hide-all-packages"
471 , "-package-env", "-"
530 , "-package-env", "-"