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 +2/−1
- lib/Hix.hs +1/−4
- lib/Hix/Bootstrap.hs +5/−5
- lib/Hix/Component.hs +68/−19
- lib/Hix/Data/GhciConfig.hs +4/−0
- lib/Hix/Data/ProjectFile.hs +2/−2
- lib/Hix/Env.hs +9/−4
- lib/Hix/Ghci.hs +17/−18
- lib/Hix/Monad.hs +2/−4
- lib/Hix/Options.hs +47/−25
- lib/Hix/Optparse.hs +7/−1
- lib/Hix/Path.hs +25/−0
- lib/Hix/Preproc.hs +9/−5
- test/Hix/Test/GhciTest.hs +13/−11
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