packages feed

hix 0.4.2 → 0.5.0

raw patch · 14 files changed

+211/−99 lines, 14 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Hix.Monad: [$sel:root:Env] :: Env -> Path Abs Dir
- Hix.Options: componentForModuleParser :: Parser ComponentCoords
- Hix.Options: instance Data.Default.Class.Default Hix.Options.GlobalOptions
+ Hix.Component: packageDefault :: PackagesConfig -> M PackageConfig
+ Hix.Component: packageForSpecOrDefault :: Path Abs Dir -> PackagesConfig -> Maybe PackageSpec -> M PackageConfig
+ Hix.Component: targetComponentIn :: Path Abs Dir -> PackagesConfig -> TargetSpec -> M Target
+ Hix.Component: targetInPackage :: PackageConfig -> Maybe ComponentSpec -> M Target
+ Hix.Component: testComponent :: ComponentSpec
+ Hix.Component: undecidableComponentError :: PackageName -> Text
+ Hix.Data.GhciConfig: ChangeDir :: Bool -> ChangeDir
+ Hix.Data.GhciConfig: [$sel:unChangeDir:ChangeDir] :: ChangeDir -> Bool
+ Hix.Data.GhciConfig: instance GHC.Classes.Eq Hix.Data.GhciConfig.ChangeDir
+ Hix.Data.GhciConfig: instance GHC.Generics.Generic Hix.Data.GhciConfig.ChangeDir
+ Hix.Data.GhciConfig: instance GHC.Show.Show Hix.Data.GhciConfig.ChangeDir
+ Hix.Data.GhciConfig: newtype ChangeDir
+ Hix.Monad: [$sel:cwd:Env] :: Env -> Path Abs Dir
+ Hix.Options: [$sel:cd:TestOptions] :: TestOptions -> ChangeDir
+ Hix.Options: [$sel:cwd:GlobalOptions] :: GlobalOptions -> Path Abs Dir
+ Hix.Options: [$sel:root:EnvRunnerOptions] :: EnvRunnerOptions -> Maybe (Path Abs Dir)
+ Hix.Options: [$sel:root:GhciOptions] :: GhciOptions -> Maybe (Path Abs Dir)
+ Hix.Options: [$sel:root:PreprocOptions] :: PreprocOptions -> Maybe (Path Abs Dir)
+ Hix.Options: cdParser :: Parser ChangeDir
+ Hix.Options: componentCoordsParser :: Parser ComponentCoords
+ Hix.Options: rootParser :: Parser (Maybe (Path Abs Dir))
+ Hix.Optparse: absDirOption :: ReadM (Path Abs Dir)
+ Hix.Path: findFlake :: Path Abs Dir -> IO (Maybe (Path Abs Dir))
+ Hix.Path: inferRoot :: M (Path Abs Dir)
+ Hix.Path: rootDir :: Maybe (Path Abs Dir) -> M (Path Abs Dir)
- Hix.Component: packageForSpec :: PackagesConfig -> PackageSpec -> M PackageConfig
+ Hix.Component: packageForSpec :: Path Abs Dir -> PackagesConfig -> PackageSpec -> M PackageConfig
- Hix.Component: targetComponent :: PackagesConfig -> TargetSpec -> M Target
+ Hix.Component: targetComponent :: Maybe (Path Abs Dir) -> PackagesConfig -> TargetSpec -> M Target
- Hix.Component: targetForComponent :: PackagesConfig -> ComponentCoords -> M Target
+ Hix.Component: targetForComponent :: Path Abs Dir -> PackagesConfig -> ComponentCoords -> M Target
- Hix.Component: targetForFile :: PackagesConfig -> Path Abs File -> M Target
+ Hix.Component: targetForFile :: Path Abs Dir -> PackagesConfig -> Path Abs File -> M Target
- Hix.Env: componentRunner :: PackagesConfig -> TargetSpec -> M (Maybe EnvRunner)
+ Hix.Env: componentRunner :: Maybe (Path Abs Dir) -> PackagesConfig -> TargetSpec -> M (Maybe EnvRunner)
- Hix.Ghci: relativeToComponent :: PackageConfig -> Maybe SourceDir -> Path Abs File -> M (Path Rel File)
+ Hix.Ghci: relativeToComponent :: Path Abs Dir -> PackageConfig -> Maybe SourceDir -> Path Abs File -> M (Path Rel File)
- Hix.Options: ComponentCoords :: PackageSpec -> ComponentSpec -> ComponentCoords
+ Hix.Options: ComponentCoords :: Maybe PackageSpec -> Maybe ComponentSpec -> ComponentCoords
- Hix.Options: EnvRunnerOptions :: Either EnvConfig JsonConfig -> Maybe TargetSpec -> EnvRunnerOptions
+ Hix.Options: EnvRunnerOptions :: Either EnvConfig JsonConfig -> Maybe (Path Abs Dir) -> Maybe TargetSpec -> EnvRunnerOptions
- Hix.Options: GhciOptions :: Either GhciConfig JsonConfig -> TargetSpec -> TestOptions -> GhciOptions
+ Hix.Options: GhciOptions :: Either GhciConfig JsonConfig -> Maybe (Path Abs Dir) -> TargetSpec -> TestOptions -> GhciOptions
- Hix.Options: GlobalOptions :: Maybe Bool -> GlobalOptions
+ Hix.Options: GlobalOptions :: Maybe Bool -> Path Abs Dir -> GlobalOptions
- Hix.Options: PreprocOptions :: Maybe (Either PreprocConfig JsonConfig) -> Path Abs File -> Path Abs File -> Path Abs File -> PreprocOptions
+ Hix.Options: PreprocOptions :: Maybe (Either PreprocConfig JsonConfig) -> Maybe (Path Abs Dir) -> Path Abs File -> Path Abs File -> Path Abs File -> PreprocOptions
- Hix.Options: TestOptions :: ModuleName -> Maybe Text -> Maybe RunnerName -> TestOptions
+ Hix.Options: TestOptions :: ModuleName -> Maybe Text -> Maybe RunnerName -> ChangeDir -> TestOptions
- Hix.Options: [$sel:component:ComponentCoords] :: ComponentCoords -> ComponentSpec
+ Hix.Options: [$sel:component:ComponentCoords] :: ComponentCoords -> Maybe ComponentSpec
- Hix.Options: [$sel:package:ComponentCoords] :: ComponentCoords -> PackageSpec
+ Hix.Options: [$sel:package:ComponentCoords] :: ComponentCoords -> Maybe PackageSpec
- Hix.Options: appParser :: Parser Options
+ Hix.Options: appParser :: Path Abs Dir -> Parser Options
- Hix.Options: componentSpecParser :: Parser ComponentSpec
+ Hix.Options: componentSpecParser :: Parser (Maybe ComponentSpec)
- Hix.Options: globalParser :: Parser GlobalOptions
+ Hix.Options: globalParser :: Path Abs Dir -> Parser GlobalOptions
- Hix.Options: packageSpecParser :: Parser PackageSpec
+ Hix.Options: packageSpecParser :: Parser (Maybe PackageSpec)
- Hix.Preproc: fromConfig :: Path Abs File -> Either PreprocConfig JsonConfig -> M CabalConfig
+ Hix.Preproc: fromConfig :: Maybe (Path Abs Dir) -> Path Abs File -> Either PreprocConfig JsonConfig -> M CabalConfig

Files

hix.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           hix-version:        0.4.2+version:        0.5.0 synopsis:       Haskell/Nix development build tools description:    See https://hackage.haskell.org/package/hix/docs/Hix.html category:       Build@@ -44,6 +44,7 @@       Hix.New       Hix.Options       Hix.Optparse+      Hix.Path       Hix.Prelude       Hix.Preproc   hs-source-dirs:
lib/Hix.hs view
@@ -1,7 +1,5 @@ module Hix where -import Path.IO (getCurrentDir)- import Hix.Bootstrap (bootstrapProject) import Hix.Data.Error (   Error (..),@@ -52,5 +50,4 @@ main :: IO () main = do   Options global cmd <- parseCli-  cwd <- getCurrentDir-  leftA (handleError global) =<< runM cwd (runCommand cmd)+  leftA (handleError global) =<< runM global.cwd (runCommand cmd)
lib/Hix/Bootstrap.hs view
@@ -150,8 +150,8 @@   Path Abs Dir ->   Path Rel File ->   M CabalInfo-readCabal root path = do-  info <- liftIO (readGenericPackageDescription Cabal.verbose (toFilePath (root </> path)))+readCabal cwd path = do+  info <- liftIO (readGenericPackageDescription Cabal.verbose (toFilePath (cwd </> path)))   pure CabalInfo {path = dir, info}   where     dir = parent path@@ -387,9 +387,9 @@  bootstrapFiles :: BootstrapProjectConfig -> M [ProjectFile] bootstrapFiles conf = do-  Env {root} <- ask-  cabals <- paths =<< lift (tryIO (getDirectoryFilesIgnore (toFilePath root) ["**/*.cabal"] ["dist-newstyle/**"]))-  pkgs <- fmap convert <$> traverse (readCabal root) cabals+  Env {cwd} <- ask+  cabals <- paths =<< lift (tryIO (getDirectoryFilesIgnore (toFilePath cwd) ["**/*.cabal"] ["dist-newstyle/**"]))+  pkgs <- fmap convert <$> traverse (readCabal cwd) cabals   pure [     ProjectFile {path = [relfile|flake.nix|], content = renderRootExpr (flake conf pkgs)}     ]
lib/Hix/Component.hs view
@@ -1,12 +1,13 @@ module Hix.Component where -import Control.Monad.Trans.Reader (ask)+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Except (throwE) import Data.List.Extra (firstJust) import qualified Data.Map.Strict as Map import Data.Map.Strict ((!?)) import qualified Data.Text as Text import Exon (exon)-import Path (Abs, Dir, File, Path, Rel, SomeBase (Abs, Rel), isProperPrefixOf, stripProperPrefix)+import Path (Abs, Dir, File, Path, Rel, SomeBase (Abs, Rel), isProperPrefixOf, reldir, stripProperPrefix)  import qualified Hix.Data.ComponentConfig import Hix.Data.ComponentConfig (@@ -17,9 +18,8 @@   SourceDir (SourceDir),   Target (Target),   )-import Hix.Data.Error (pathText)-import qualified Hix.Monad as Monad-import Hix.Monad (Env (Env), M, noteEnv)+import Hix.Data.Error (Error (EnvError), pathText)+import Hix.Monad (M, noteEnv) import qualified Hix.Options as Options import Hix.Options (   ComponentCoords,@@ -27,6 +27,7 @@   PackageSpec (PackageSpec),   TargetSpec (TargetForComponent, TargetForFile),   )+import Hix.Path (rootDir)  tryPackageByDir ::   PackagesConfig ->@@ -44,13 +45,18 @@ packageByDir config dir =   noteEnv [exon|No package at this directory: #{pathText dir}|] (tryPackageByDir config dir) +packageDefault :: PackagesConfig -> M PackageConfig+packageDefault = \case+  [(_, pkg)] -> pure pkg+  _ -> lift (throwE (EnvError "Project has more than one package, specify -p or -f."))+ packageForSpec ::+  Path Abs Dir ->   PackagesConfig ->   PackageSpec ->   M PackageConfig-packageForSpec config = \case+packageForSpec root config = \case   PackageSpec _ (Just (Abs dir)) -> do-    Env {root} <- ask     rel <- noteEnv [exon|Path is not a subdirectory of the project root: #{pathText dir}|] (stripProperPrefix root dir)     packageByDir config rel   PackageSpec (PackageName name) (Just (Rel dir)) | Text.elem '/' name ->@@ -62,6 +68,15 @@         Abs _ -> Nothing         Rel rd -> tryPackageByDir config rd +packageForSpecOrDefault ::+  Path Abs Dir ->+  PackagesConfig ->+  Maybe PackageSpec ->+  M PackageConfig+packageForSpecOrDefault root config = \case+  Just pkg -> packageForSpec root config pkg+  Nothing -> packageDefault config+ matchComponent :: ComponentConfig -> ComponentSpec -> Bool matchComponent candidate (ComponentSpec name dir) =   candidate.name == name || any (\ d -> elem @[] d (coerce candidate.sourceDirs)) dir@@ -72,23 +87,47 @@   where     name = spec.name +undecidableComponentError :: PackageName -> Text+undecidableComponentError pname =+  [exon|Please specify a component name or source dir with -c for the package '##{pname}'|]++testComponent :: ComponentSpec+testComponent =+  ComponentSpec "test" (Just (SourceDir [reldir|test|]))++targetInPackage ::+  PackageConfig ->+  Maybe ComponentSpec ->+  M Target+targetInPackage package = \case+  Just comp -> do+    component <- noteEnv (componentError package.name comp) (find match (Map.elems package.components))+    pure Target {sourceDir = Nothing, ..}+    where+      match cand = matchComponent cand comp+  Nothing -> do+    component <- noteEnv (undecidableComponentError package.name) (selectComponent package.components)+    pure Target {sourceDir = Nothing, ..}+    where+      selectComponent [(_, comp)] = Just comp+      selectComponent (Map.elems -> comps) = find (match testComponent) comps+      match = flip matchComponent+ targetForComponent ::+  Path Abs Dir ->   PackagesConfig ->   ComponentCoords ->   M Target-targetForComponent config spec = do-  package <- packageForSpec config spec.package-  component <- noteEnv (componentError package.name spec.component) (find match (Map.elems package.components))-  pure Target {sourceDir = Nothing, ..}-  where-    match = flip matchComponent spec.component+targetForComponent root config spec = do+  package <- packageForSpecOrDefault root config spec.package+  targetInPackage package spec.component  targetForFile ::+  Path Abs Dir ->   PackagesConfig ->   Path Abs File ->   M Target-targetForFile config file = do-  Env {root} <- ask+targetForFile root config file = do   fileRel <- stripProperPrefix root file   (package, subpath) <- pkgError (firstJust (matchPackage fileRel) (Map.elems config))   (component, sourceDir) <- compError (firstJust (matchSourceDir subpath) (Map.elems package.components))@@ -104,12 +143,22 @@     pkgError = noteEnv "No package contains this file"     compError = noteEnv "No component source dir contains this file" -targetComponent ::+targetComponentIn ::+  Path Abs Dir ->   PackagesConfig ->   TargetSpec ->   M Target-targetComponent config = \case+targetComponentIn root config = \case   TargetForComponent spec ->-    targetForComponent config spec+    targetForComponent root config spec   TargetForFile spec ->-    targetForFile config spec+    targetForFile root config spec++targetComponent ::+  Maybe (Path Abs Dir) ->+  PackagesConfig ->+  TargetSpec ->+  M Target+targetComponent cliRoot config spec = do+  root <- rootDir cliRoot+  targetComponentIn root config spec
lib/Hix/Data/GhciConfig.hs view
@@ -25,6 +25,10 @@   deriving stock (Eq, Show, Generic)   deriving newtype (IsList, Ord, FromJSON) +newtype ChangeDir =+  ChangeDir { unChangeDir :: Bool }+  deriving stock (Eq, Show, Generic)+ data EnvConfig =   EnvConfig {     packages :: PackagesConfig,
lib/Hix/Data/ProjectFile.hs view
@@ -19,9 +19,9 @@  createFile :: ProjectFile -> M () createFile f = do-  Env {root} <- ask+  Env {cwd} <- ask   let-    file = root </> f.path+    file = cwd </> f.path   lift $ tryIO do     createDirIfMissing True (parent file)     Text.writeFile (toFilePath file) f.content
lib/Hix/Env.hs view
@@ -1,6 +1,7 @@ module Hix.Env where  import qualified Data.Text.IO as Text+import Path (Abs, Dir, Path)  import Hix.Component (targetComponent) import qualified Hix.Data.ComponentConfig@@ -12,15 +13,19 @@ import qualified Hix.Options as Options import Hix.Options (EnvRunnerOptions, TargetSpec) -componentRunner :: PackagesConfig -> TargetSpec -> M (Maybe EnvRunner)-componentRunner config spec = do-  Target {component} <- targetComponent config spec+componentRunner ::+  Maybe (Path Abs Dir) ->+  PackagesConfig ->+  TargetSpec ->+  M (Maybe EnvRunner)+componentRunner cliRoot config spec = do+  Target {component} <- targetComponent cliRoot config spec   pure component.runner  envRunner :: EnvRunnerOptions -> M EnvRunner envRunner opts = do   config <- either pure jsonConfig opts.config-  fromMaybe config.defaultEnv . join <$> traverse (componentRunner config.packages) opts.component+  fromMaybe config.defaultEnv . join <$> traverse (componentRunner opts.root config.packages) opts.component  printEnvRunner :: EnvRunnerOptions -> M () printEnvRunner opts = do
lib/Hix/Ghci.hs view
@@ -2,7 +2,6 @@  import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Except (ExceptT, catchE)-import Control.Monad.Trans.Reader (ask) import Data.List.Extra (nubOrd) import qualified Data.Map.Strict as Map import Data.Map.Strict ((!?))@@ -10,7 +9,7 @@ import qualified Data.Text.IO as Text import Exon (exon) import Path (Abs, Dir, File, Path, Rel, parseRelDir, reldir, splitExtension, stripProperPrefix, toFilePath, (</>))-import Path.IO (createDirIfMissing, getCurrentDir, getTempDir, openTempFile)+import Path.IO (createDirIfMissing, getTempDir, openTempFile) import System.IO (hClose) import System.Posix.User (getLoginName) @@ -30,22 +29,18 @@ import qualified Hix.Data.GhciTest as GhciTest import Hix.Data.GhciTest (GhciRun (GhciRun), GhciTest (GhciTest), GhcidRun (GhcidRun)) import Hix.Json (jsonConfig)-import qualified Hix.Monad as Monad-import Hix.Monad (Env (Env), M, noteGhci, tryIOM)+import Hix.Monad (M, noteGhci) import qualified Hix.Options as Options-import Hix.Options (-  GhciOptions (GhciOptions),-  TargetSpec (TargetForComponent, TargetForFile),-  TestOptions (TestOptions),-  )+import Hix.Options (GhciOptions (GhciOptions), TargetSpec (TargetForFile), TestOptions (TestOptions))+import Hix.Path (rootDir)  relativeToComponent ::+  Path Abs Dir ->   PackageConfig ->   Maybe SourceDir ->   Path Abs File ->   M (Path Rel File)-relativeToComponent package mdir path = do-  Env {root} <- ask+relativeToComponent root package mdir path = do   SourceDir dir <- noteGhci "Internal: No source dir for file target" mdir   noteGhci "Internal: Bad file target" (stripProperPrefix (root </> package.src </> dir) path) @@ -55,10 +50,11 @@   GhciOptions ->   M ModuleName moduleName package component = \case-  GhciOptions {component = TargetForComponent _, test} -> pure test.mod-  GhciOptions {component = TargetForFile path} -> do-    rel <- relativeToComponent package component path+  GhciOptions {component = TargetForFile path, root = cliRoot} -> do+    root <- rootDir cliRoot+    rel <- relativeToComponent root package component path     pure (ModuleName (Text.replace "/" "." (withoutExt rel)))+  GhciOptions {test} -> pure test.mod   where     withoutExt p = pathText (maybe p fst (splitExtension p)) @@ -70,10 +66,13 @@   M Text ghciScript config package component opt = do   ModuleName module_ <- moduleName package component opt-  pure [exon|#{setup}+  pure [exon|#{cdCode}#{setup} :load #{module_} import #{module_}|]   where+    cdCode | opt.test.cd.unChangeDir = [exon|:cd #{pathText package.src}+|]+           | otherwise = ""     GhciSetupCode setup = fold (flip Map.lookup config.setup =<< opt.test.runner)  componentSearchPaths :: PackageConfig -> ComponentConfig -> [Path Rel Dir]@@ -103,14 +102,14 @@ assemble :: GhciOptions -> M GhciTest assemble opt = do   config <- either pure jsonConfig opt.config-  cwd <- tryIOM getCurrentDir-  Target {..} <- targetComponent config.packages opt.component+  root <- rootDir opt.root+  Target {..} <- targetComponent opt.root config.packages opt.component   script <- ghciScript config package sourceDir opt   pure GhciTest {     script,     test = testRun config opt.test,     args = config.args,-    searchPath = (cwd </>) <$> searchPath config.packages package component+    searchPath = (root </>) <$> searchPath config.packages package component     }  hixTempDir ::
lib/Hix/Monad.hs view
@@ -9,7 +9,7 @@  data Env =   Env {-    root :: Path Abs Dir+    cwd :: Path Abs Dir   }   deriving stock (Eq, Show, Generic) @@ -35,8 +35,6 @@ runM root ma =   runExceptT (runReaderT ma (Env root)) -tryIOM ::-  IO a ->-  M a+tryIOM :: IO a -> M a tryIOM ma =   lift (tryIO ma)
lib/Hix/Options.hs view
@@ -26,6 +26,7 @@   value,   ) import Path (Abs, Dir, File, Path, SomeBase, parseRelDir, parseSomeDir)+import Path.IO (getCurrentDir) import Prelude hiding (Mod, mod)  import qualified Hix.Data.BootstrapProjectConfig@@ -37,15 +38,16 @@   PackageName (PackageName),   SourceDir (SourceDir),   )-import Hix.Data.GhciConfig (EnvConfig, GhciConfig, RunnerName)+import Hix.Data.GhciConfig (ChangeDir (ChangeDir), EnvConfig, GhciConfig, RunnerName) import qualified Hix.Data.NewProjectConfig import Hix.Data.NewProjectConfig (NewProjectConfig (NewProjectConfig)) import Hix.Data.PreprocConfig (PreprocConfig)-import Hix.Optparse (JsonConfig, absFileOption, jsonOption)+import Hix.Optparse (JsonConfig, absDirOption, absFileOption, jsonOption)  data PreprocOptions =   PreprocOptions {     config :: Maybe (Either PreprocConfig JsonConfig),+    root :: Maybe (Path Abs Dir),     source :: Path Abs File,     inFile :: Path Abs File,     outFile :: Path Abs File@@ -68,8 +70,8 @@  data ComponentCoords =   ComponentCoords {-    package :: PackageSpec,-    component :: ComponentSpec+    package :: Maybe PackageSpec,+    component :: Maybe ComponentSpec   }   deriving stock (Eq, Show, Generic) @@ -83,13 +85,15 @@   TestOptions {     mod :: ModuleName,     test :: Maybe Text,-    runner :: Maybe RunnerName+    runner :: Maybe RunnerName,+    cd :: ChangeDir   }   deriving stock (Eq, Show, Generic)  data EnvRunnerOptions =   EnvRunnerOptions {     config :: Either EnvConfig JsonConfig,+    root :: Maybe (Path Abs Dir),     component :: Maybe TargetSpec   }   deriving stock (Show, Generic)@@ -97,6 +101,7 @@ data GhciOptions =   GhciOptions {     config :: Either GhciConfig JsonConfig,+    root :: Maybe (Path Abs Dir),     component :: TargetSpec,     test :: TestOptions   }@@ -137,10 +142,10 @@  data GlobalOptions =   GlobalOptions {-    verbose :: Maybe Bool+    verbose :: Maybe Bool,+    cwd :: Path Abs Dir   }   deriving stock (Eq, Show, Generic)-  deriving anyclass (Default)  data Options =   Options {@@ -156,10 +161,14 @@ fileParser longName helpText =   option absFileOption (long longName <> completer (bashCompleter "file") <> help helpText) +rootParser :: Parser (Maybe (Path Abs Dir))+rootParser =+  optional (option absDirOption (long "root" <> help "The root directory of the project"))+ jsonConfigParser ::   Parser JsonConfig jsonConfigParser =-  option jsonOption (long "config" <> short 'c' <> help "The Hix-generated config, file or text")+  option jsonOption (long "config" <> help "The Hix-generated config, file or text")  preprocParser :: Parser PreprocOptions preprocParser =@@ -167,26 +176,28 @@   <$>   (fmap Right <$> optional jsonConfigParser)   <*>+  rootParser+  <*>   fileParser "source" "The original source file"   <*>   fileParser "in" "The prepared input file"   <*>   fileParser "out" "The path to the output file" -packageSpecParser :: Parser PackageSpec+packageSpecParser :: Parser (Maybe PackageSpec) packageSpecParser = do-  name <- strOption (long "package" <> short 'p' <> help "The name or directory of the test package")-  pure PackageSpec {name = PackageName name, dir = parseSomeDir (toString name)}+  optional (strOption (long "package" <> short 'p' <> help "The name or directory of the test package")) <&> fmap \ name ->+    PackageSpec {name = PackageName name, dir = parseSomeDir (toString name)} -componentSpecParser :: Parser ComponentSpec+componentSpecParser :: Parser (Maybe ComponentSpec) componentSpecParser = do-  name <- strOption (long "component" <> short 'c' <> help h <> value "test")-  pure ComponentSpec {name = ComponentName name, dir = SourceDir <$> parseRelDir (toString name)}+  optional (strOption (long "component" <> short 'c' <> help h)) <&> fmap \ name ->+    ComponentSpec {name = ComponentName name, dir = SourceDir <$> parseRelDir (toString name)}   where     h = "The name or relative directory of the test component" -componentForModuleParser :: Parser ComponentCoords-componentForModuleParser =+componentCoordsParser :: Parser ComponentCoords+componentCoordsParser =   ComponentCoords   <$>   packageSpecParser@@ -201,9 +212,9 @@  targetSpecParser :: Parser TargetSpec targetSpecParser =-  TargetForComponent <$> componentForModuleParser-  <|>   componentForFileParser+  <|>+  TargetForComponent <$> componentCoordsParser  envNameParser :: Parser EnvName envNameParser =@@ -223,6 +234,10 @@     help "The name of the command defined in the Hix option 'ghci.run'"   )) +cdParser :: Parser ChangeDir+cdParser =+  ChangeDir . not <$> switch (long "no-cd" <> help "Don't change the working directory to the package root")+ moduleParser :: Parser ModuleName moduleParser =   strOption (long "module" <> short 'm' <> help "The module containing the test function" <> value "Main")@@ -232,12 +247,14 @@   test <- testParser   runner <- runnerParser   mod <- moduleParser+  cd <- cdParser   pure TestOptions {..}  envParser :: Parser EnvRunnerCommandOptions envParser = do   options <- do     config <- Right <$> jsonConfigParser+    root <- rootParser     component <- optional targetSpecParser     pure EnvRunnerOptions {..}   test <- testOptionsParser@@ -246,6 +263,7 @@ ghciParser :: Parser GhciOptions ghciParser = do   config <- Right <$> jsonConfigParser+  root <- rootParser   component <- targetSpecParser   test <- testOptionsParser   pure GhciOptions {..}@@ -263,8 +281,7 @@   hixUrl <- strOption (long "hix-url" <> help "The URL to the Hix repository" <> value def)   pure BootstrapOptions {config = BootstrapProjectConfig {..}} -commands ::-  Mod CommandFields Command+commands :: Mod CommandFields Command commands =   command "preproc" (Preproc <$> info preprocParser (progDesc "Preprocess a source file for use with ghcid"))   <>@@ -278,20 +295,25 @@   <>   command "bootstrap" (BootstrapCmd <$> info bootstrapParser (progDesc "Bootstrap an existing Cabal project in the current directory")) -globalParser :: Parser GlobalOptions-globalParser = do+globalParser ::+  Path Abs Dir ->+  Parser GlobalOptions+globalParser realCwd = do   verbose <- optional (switch (long "verbose" <> short 'v' <> help "Verbose output"))+  cwd <- option absDirOption (long "cwd" <> help "Force a different working directory" <> value realCwd)   pure GlobalOptions {..}  appParser ::+  Path Abs Dir ->   Parser Options-appParser =-  Options <$> globalParser <*> hsubparser commands+appParser cwd =+  Options <$> globalParser cwd <*> hsubparser commands  parseCli ::   IO Options parseCli = do-  customExecParser parserPrefs (info (appParser <**> helper) desc)+  realCwd <- getCurrentDir+  customExecParser parserPrefs (info (appParser realCwd <**> helper) desc)   where     parserPrefs =       prefs (showHelpOnEmpty <> showHelpOnError)
lib/Hix/Optparse.hs view
@@ -5,7 +5,7 @@ import Exon (exon) import Options.Applicative (ReadM, readerError) import Options.Applicative.Types (readerAsk)-import Path (Abs, Dir, File, Path, Rel, parseAbsFile, parseRelDir, parseRelFile, toFilePath)+import Path (Abs, Dir, File, Path, Rel, parseAbsDir, parseAbsFile, parseRelDir, parseRelFile, toFilePath) import qualified Text.Show as Show  -- |An absolute file path option for @optparse-applicative@.@@ -19,6 +19,12 @@ relFileOption = do   raw <- readerAsk   leftA (const (readerError [exon|not a valid relative file path: #{raw}|])) (parseRelFile raw)++-- |A relative dir path option for @optparse-applicative@.+absDirOption :: ReadM (Path Abs Dir)+absDirOption = do+  raw <- readerAsk+  leftA (const (readerError [exon|not a valid absolute dir path: #{raw}|])) (parseAbsDir raw)  -- |A relative dir path option for @optparse-applicative@. relDirOption :: ReadM (Path Rel Dir)
+ lib/Hix/Path.hs view
@@ -0,0 +1,25 @@+module Hix.Path where++import Control.Monad.Trans.Reader (ask)+import Path (Abs, Dir, Path, parent, toFilePath)+import System.FilePattern.Directory (getDirectoryFiles)+import System.IO.Error (tryIOError)++import qualified Hix.Monad+import Hix.Monad (Env (Env), M)++findFlake :: Path Abs Dir -> IO (Maybe (Path Abs Dir))+findFlake cur =+  tryIOError (getDirectoryFiles (toFilePath cur) ["flake.nix"]) >>= either (const (pure Nothing)) \case+    [_] -> pure (Just cur)+    _ | parent cur == cur -> pure Nothing+    _ | otherwise -> findFlake (parent cur)++inferRoot :: M (Path Abs Dir)+inferRoot = do+  Env {cwd} <- ask+  fromMaybe cwd <$> liftIO (findFlake cwd)++rootDir :: Maybe (Path Abs Dir) -> M (Path Abs Dir)+rootDir =+  maybe inferRoot pure
lib/Hix/Preproc.hs view
@@ -17,7 +17,7 @@   Extension (DisableExtension, EnableExtension, UnknownExtension),   Language (UnknownLanguage),   )-import Path (Abs, File, Path, toFilePath)+import Path (Abs, Dir, File, Path, toFilePath) import Prelude hiding (group) import System.Random (randomRIO) @@ -446,10 +446,14 @@   let result = preprocessModule opt.source conf dummyExportName inLines   lift (tryIO (ByteStringBuilder.writeFile (toFilePath opt.outFile) result)) -fromConfig :: Path Abs File -> Either PreprocConfig JsonConfig -> M CabalConfig-fromConfig source pconf = do+fromConfig ::+  Maybe (Path Abs Dir) ->+  Path Abs File ->+  Either PreprocConfig JsonConfig ->+  M CabalConfig+fromConfig cliRoot source pconf = do   conf <- either pure jsonConfig pconf-  target <- targetComponent conf.packages (TargetForFile source)+  target <- targetComponent cliRoot conf.packages (TargetForFile source)   pure CabalConfig {     extensions = stringUtf8 <$> target.component.language : target.component.extensions,     ghcOptions = stringUtf8 <$> target.component.ghcOptions,@@ -476,5 +480,5 @@ -- TODO add common stanzas preprocess :: PreprocOptions -> M () preprocess opt = do-  conf <- maybe (fromCabalFile opt.source) (fromConfig opt.source) opt.config+  conf <- maybe (fromCabalFile opt.source) (fromConfig opt.root opt.source) opt.config   preprocessWith opt conf
test/Hix/Test/GhciTest.hs view
@@ -4,7 +4,7 @@ import Exon (exon) import Hedgehog (TestT, evalEither, (===)) import Path (Abs, Dir, File, Path, Rel, SomeBase (Rel), absdir, absfile, reldir, relfile, (</>))-import Path.IO (getCurrentDir, withSystemTempDir)+import Path.IO (withSystemTempDir)  import Hix.Data.ComponentConfig (   ComponentConfig (..),@@ -16,7 +16,7 @@   SourceDirs (SourceDirs),   ) import Hix.Data.Error (pathText)-import Hix.Data.GhciConfig (EnvConfig (EnvConfig), GhciConfig (..))+import Hix.Data.GhciConfig (ChangeDir (ChangeDir), EnvConfig (EnvConfig), GhciConfig (..)) import qualified Hix.Data.GhciTest as GhciTest import Hix.Env (envRunner) import Hix.Ghci (assemble, ghcidCmdlineFromOptions)@@ -85,8 +85,8 @@ spec1 :: TargetSpec spec1 =   TargetForComponent ComponentCoords {-    package = PackageSpec "api" (Just (Rel [reldir|api|])),-    component = ComponentSpec "test" (Just (SourceDir [reldir|test|]))+    package = Just (PackageSpec "api" (Just (Rel [reldir|api|]))),+    component = Just (ComponentSpec "test" (Just (SourceDir [reldir|test|])))   }  options :: GhciOptions@@ -98,11 +98,13 @@       run = [("generic", ("check . property . test"))],       args = ["-Werror"]     },+    root = Nothing,     component = spec1,     test = TestOptions {       mod = "Api.ServerTest",       test = Just "test_server",-      runner = Just "generic"+      runner = Just "generic",+      cd = ChangeDir True     }   } @@ -119,11 +121,10 @@  test_ghcid :: TestT IO () test_ghcid = do-  cwd <- getCurrentDir   res <- lift $ withSystemTempDir "hix-test" \ tmp ->     runM root (ghcidCmdlineFromOptions tmp options)   cmdline <- evalEither res-  ghcidTarget cwd cmdline.ghci.scriptFile === cmdline.cmdline+  ghcidTarget root cmdline.ghci.scriptFile === cmdline.cmdline  spec2 :: TargetSpec spec2 =@@ -132,8 +133,8 @@ spec3 :: TargetSpec spec3 =   TargetForComponent ComponentCoords {-    package = PackageSpec "packages/core" (Just (Rel [reldir|packages/core|])),-    component = ComponentSpec "core-test" (Just (SourceDir [reldir|core-test|]))+    package = Just (PackageSpec "packages/core" (Just (Rel [reldir|packages/core|]))),+    component = Just (ComponentSpec "core-test" (Just (SourceDir [reldir|core-test|])))   }  runnerFor :: EnvRunner -> TargetSpec -> TestT IO ()@@ -141,7 +142,7 @@   res <- evalEither =<< liftIO (runM root (envRunner conf))   target === res   where-    conf = EnvRunnerOptions (Left (EnvConfig packages defaultRunner)) (Just spec)+    conf = EnvRunnerOptions (Left (EnvConfig packages defaultRunner)) Nothing (Just spec)  test_componentEnv :: TestT IO () test_componentEnv = do@@ -155,7 +156,8 @@  target_moduleName :: Text target_moduleName =-  [exon|import Test.Tasty+  [exon|:cd packages/core/+import Test.Tasty :load #{m} import #{m}|]   where