diff --git a/Cabal-syntax/src/Distribution/Fields/ConfVar.hs b/Cabal-syntax/src/Distribution/Fields/ConfVar.hs index 68601cd53e9..b045c3ef172 100644 --- a/Cabal-syntax/src/Distribution/Fields/ConfVar.hs +++ b/Cabal-syntax/src/Distribution/Fields/ConfVar.hs @@ -1,14 +1,15 @@ {-# LANGUAGE OverloadedStrings #-} -module Distribution.Fields.ConfVar (parseConditionConfVar) where +module Distribution.Fields.ConfVar (parseConditionConfVar, parseConditionConfVarFromClause) where import Distribution.Compat.CharParsing (char, integral) import Distribution.Compat.Prelude -import Distribution.Fields.Field (SectionArg (..)) +import Distribution.Fields.Field (SectionArg (..), Field(..)) import Distribution.Fields.ParseResult import Distribution.Parsec (Parsec (..), Position (..), runParsecParser) import Distribution.Parsec.FieldLineStream (fieldLineStreamFromBS) import Distribution.Types.Condition import Distribution.Types.ConfVar (ConfVar (..)) +import Distribution.Fields.Parser (readFields) import Distribution.Version (anyVersion, earlierVersion, intersectVersionRanges, laterVersion, majorBoundVersion, mkVersion, noVersion, orEarlierVersion, orLaterVersion, thisVersion, unionVersionRanges, @@ -16,7 +17,14 @@ import Distribution.Version import Prelude () import qualified Text.Parsec as P +import qualified Text.Parsec.Pos as P import qualified Text.Parsec.Error as P +import qualified Data.ByteString.Char8 as B8 + +parseConditionConfVarFromClause :: B8.ByteString -> Either P.ParseError (Condition ConfVar) +parseConditionConfVarFromClause x = readFields x >>= \r -> case r of + (Section _ xs _ : _ ) -> P.runParser (parser <* P.eof) () "" xs + _ -> Left $ P.newErrorMessage (P.Message "No fields in clause") (P.initialPos "") -- | Parse @'Condition' 'ConfVar'@ from section arguments provided by parsec -- based outline parser. diff --git a/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs b/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs index 3a52ca783db..b9bb9dcb555 100644 --- a/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs +++ b/Cabal-syntax/src/Distribution/PackageDescription/Configuration.hs @@ -32,6 +32,7 @@ module Distribution.PackageDescription.Configuration ( transformAllBuildInfos, transformAllBuildDepends, transformAllBuildDependsN, + simplifyWithSysParams ) where import Distribution.Compat.Prelude diff --git a/Cabal-syntax/src/Distribution/Types/CondTree.hs b/Cabal-syntax/src/Distribution/Types/CondTree.hs index 8fd233658f5..8f1d17a8163 100644 --- a/Cabal-syntax/src/Distribution/Types/CondTree.hs +++ b/Cabal-syntax/src/Distribution/Types/CondTree.hs @@ -68,6 +68,13 @@ instance (Binary v, Binary c, Binary a) => Binary (CondTree v c a) instance (Structured v, Structured c, Structured a) => Structured (CondTree v c a) instance (NFData v, NFData c, NFData a) => NFData (CondTree v c a) where rnf = genericRnf +instance (Semigroup a, Semigroup c) => Semigroup (CondTree v c a) where + (CondNode a c bs) <> (CondNode a' c' bs') = CondNode (a <> a') (c <> c') (bs <> bs') + +instance (Semigroup a, Semigroup c, Monoid a, Monoid c) => Monoid (CondTree v c a) where + mappend = (<>) + mempty = CondNode mempty mempty mempty + -- | A 'CondBranch' represents a conditional branch, e.g., @if -- flag(foo)@ on some syntax @a@. It also has an optional false -- branch. @@ -191,4 +198,4 @@ foldCondTree e u mergeInclusive mergeExclusive = goTree goTree :: CondTree v c a -> b goTree (CondNode a c ifs) = u (c, a) `mergeInclusive` foldl goBranch e ifs goBranch :: b -> CondBranch v c a -> b - goBranch acc (CondBranch _ t mt) = mergeInclusive acc (maybe (goTree t) (mergeExclusive (goTree t) . goTree) mt) + goBranch acc (CondBranch _ t mt) = mergeInclusive acc (maybe (goTree t) (mergeExclusive (goTree t) . goTree) mt) \ No newline at end of file diff --git a/cabal-install/src/Distribution/Client/CmdConfigure.hs b/cabal-install/src/Distribution/Client/CmdConfigure.hs index e75f25d0486..a17d7cd2f7d 100644 --- a/cabal-install/src/Distribution/Client/CmdConfigure.hs +++ b/cabal-install/src/Distribution/Client/CmdConfigure.hs @@ -30,12 +30,17 @@ import Distribution.Verbosity import Distribution.Simple.Command ( CommandUI(..), usageAlternatives ) import Distribution.Simple.Utils - ( wrapText, notice ) + ( wrapText, notice, die' ) import Distribution.Client.DistDirLayout ( DistDirLayout(..) ) import Distribution.Client.RebuildMonad (runRebuild) import Distribution.Client.ProjectConfig.Types +import Distribution.Client.HttpUtils +import Distribution.Utils.NubList + ( fromNubList ) +import Distribution.Types.CondTree + ( CondTree (..) ) configureCommand :: CommandUI (NixStyleFlags ()) configureCommand = CommandUI { @@ -126,8 +131,12 @@ configureAction' flags@NixStyleFlags {..} _extraArgs globalFlags = do -- If the flag @configAppend@ is set to true, append and do not overwrite if exists && appends then do - conf <- runRebuild (distProjectRootDirectory . distDirLayout $ baseCtx) $ - readProjectLocalExtraConfig v (distDirLayout baseCtx) + httpTransport <- configureTransport v + (fromNubList . projectConfigProgPathExtra $ projectConfigShared cliConfig) + (flagToMaybe . projectConfigHttpTransport $ projectConfigBuildOnly cliConfig) + (CondNode conf imps bs) <- runRebuild (distProjectRootDirectory . distDirLayout $ baseCtx) $ + readProjectLocalExtraConfig v httpTransport (distDirLayout baseCtx) + when (not (null imps && null bs)) $ die' v "local project file has conditional and/or import logic, unable to perform and automatic in-place update" return (baseCtx, conf <> cliConfig) else return (baseCtx, cliConfig) diff --git a/cabal-install/src/Distribution/Client/CmdOutdated.hs b/cabal-install/src/Distribution/Client/CmdOutdated.hs index d03d759bebc..c511c53c1c6 100644 --- a/cabal-install/src/Distribution/Client/CmdOutdated.hs +++ b/cabal-install/src/Distribution/Client/CmdOutdated.hs @@ -29,9 +29,8 @@ import Distribution.Client.DistDirLayout ( defaultDistDirLayout , DistDirLayout(distProjectRootDirectory, distProjectFile) ) import Distribution.Client.ProjectConfig - ( ProjectConfig(projectConfigShared), - ProjectConfigShared(projectConfigConstraints), findProjectRoot, - readProjectLocalFreezeConfig ) +import Distribution.Client.ProjectConfig.Legacy + ( instantiateProjectConfigSkeleton ) import Distribution.Client.ProjectFlags ( projectFlagsOptions, ProjectFlags(..), defaultProjectFlags , removeIgnoreProjectOption ) @@ -40,8 +39,6 @@ import Distribution.Client.RebuildMonad import Distribution.Client.Sandbox ( loadConfigOrSandboxConfig ) import Distribution.Client.Setup - ( withRepoContext, GlobalFlags, configCompilerAux' - , ConfigExFlags(configExConstraints) ) import Distribution.Client.Targets ( userToPackageConstraint, UserConstraint ) import Distribution.Client.Types.SourcePackageDb as SourcePackageDb @@ -65,7 +62,7 @@ import Distribution.Simple.Setup import Distribution.Simple.Utils ( die', notice, debug, tryFindPackageDesc ) import Distribution.System - ( Platform ) + ( Platform (..) ) import Distribution.Types.ComponentRequestedSpec ( ComponentRequestedSpec(..) ) import Distribution.Types.Dependency @@ -86,6 +83,9 @@ import Distribution.Simple.PackageDescription import qualified Distribution.Compat.CharParsing as P import Distribution.ReadE ( parsecToReadE ) +import Distribution.Client.HttpUtils +import Distribution.Utils.NubList + ( fromNubList ) import qualified Data.Set as S import System.Directory @@ -220,18 +220,23 @@ outdatedAction (ProjectFlags{flagProjectFileName}, OutdatedFlags{..}) _targetStr config <- loadConfigOrSandboxConfig verbosity globalFlags let globalFlags' = savedGlobalFlags config `mappend` globalFlags configFlags = savedConfigureFlags config - (comp, platform, _progdb) <- configCompilerAux' configFlags withRepoContext verbosity globalFlags' $ \repoContext -> do when (not newFreezeFile && isJust mprojectFile) $ die' verbosity $ "--project-file must only be used with --v2-freeze-file." sourcePkgDb <- IndexUtils.getSourcePackages verbosity repoContext + (comp, platform, _progdb) <- configCompilerAux' configFlags deps <- if freezeFile then depsFromFreezeFile verbosity else if newFreezeFile - then depsFromNewFreezeFile verbosity mprojectFile - else depsFromPkgDesc verbosity comp platform + then do + httpTransport <- configureTransport verbosity + (fromNubList . globalProgPathExtra $ globalFlags) + (flagToMaybe . globalHttpTransport $ globalFlags) + depsFromNewFreezeFile verbosity httpTransport comp platform mprojectFile + else do + depsFromPkgDesc verbosity comp platform debug verbosity $ "Dependencies loaded: " ++ intercalate ", " (map prettyShow deps) let outdatedDeps = listOutdated deps sourcePkgDb @@ -293,14 +298,15 @@ depsFromFreezeFile verbosity = do return deps -- | Read the list of dependencies from the new-style freeze file. -depsFromNewFreezeFile :: Verbosity -> Maybe FilePath -> IO [PackageVersionConstraint] -depsFromNewFreezeFile verbosity mprojectFile = do +depsFromNewFreezeFile :: Verbosity -> HttpTransport -> Compiler -> Platform -> Maybe FilePath -> IO [PackageVersionConstraint] +depsFromNewFreezeFile verbosity httpTransport compiler (Platform arch os) mprojectFile = do projectRoot <- either throwIO return =<< findProjectRoot Nothing mprojectFile let distDirLayout = defaultDistDirLayout projectRoot {- TODO: Support dist dir override -} Nothing - projectConfig <- runRebuild (distProjectRootDirectory distDirLayout) $ - readProjectLocalFreezeConfig verbosity distDirLayout + projectConfig <- runRebuild (distProjectRootDirectory distDirLayout) $ do + pcs <- readProjectLocalFreezeConfig verbosity httpTransport distDirLayout + pure $ instantiateProjectConfigSkeleton os arch (compilerInfo compiler) mempty pcs let ucnstrs = map fst . projectConfigConstraints . projectConfigShared $ projectConfig deps = userConstraintsToDependencies ucnstrs diff --git a/cabal-install/src/Distribution/Client/ParseUtils.hs b/cabal-install/src/Distribution/Client/ParseUtils.hs index bf71fb6b00b..0b8e45c5641 100644 --- a/cabal-install/src/Distribution/Client/ParseUtils.hs +++ b/cabal-install/src/Distribution/Client/ParseUtils.hs @@ -369,4 +369,3 @@ parseConfig fieldDescrs sectionDescrs fgSectionDescrs empty str = -- showConfig :: [FieldDescr a] -> [SectionDescr a] -> [FGSectionDescr FG.PrettyFieldGrammar a] -> a -> Disp.Doc showConfig = ppFieldsAndSections - diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index 2680707f411..9ca62bd63e7 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -29,7 +29,6 @@ module Distribution.Client.ProjectConfig ( readGlobalConfig, readProjectLocalExtraConfig, readProjectLocalFreezeConfig, - parseProjectConfig, reportParseResult, showProjectConfig, withProjectOrGlobalConfig, @@ -504,14 +503,15 @@ withProjectOrGlobalConfig' verbosity globalConfigFlag with without = do -- file if any, plus other global config. -- readProjectConfig :: Verbosity + -> HttpTransport -> Flag FilePath -> DistDirLayout - -> Rebuild ProjectConfig -readProjectConfig verbosity configFileFlag distDirLayout = do - global <- readGlobalConfig verbosity configFileFlag - local <- readProjectLocalConfigOrDefault verbosity distDirLayout - freeze <- readProjectLocalFreezeConfig verbosity distDirLayout - extra <- readProjectLocalExtraConfig verbosity distDirLayout + -> Rebuild ProjectConfigSkeleton +readProjectConfig verbosity httpTransport configFileFlag distDirLayout = do + global <- singletonProjectConfigSkeleton <$> readGlobalConfig verbosity configFileFlag + local <- readProjectLocalConfigOrDefault verbosity httpTransport distDirLayout + freeze <- readProjectLocalFreezeConfig verbosity httpTransport distDirLayout + extra <- readProjectLocalExtraConfig verbosity httpTransport distDirLayout return (global <> local <> freeze <> extra) @@ -519,16 +519,17 @@ readProjectConfig verbosity configFileFlag distDirLayout = do -- or returns the default project config for an implicitly defined project. -- readProjectLocalConfigOrDefault :: Verbosity + -> HttpTransport -> DistDirLayout - -> Rebuild ProjectConfig -readProjectLocalConfigOrDefault verbosity distDirLayout = do + -> Rebuild ProjectConfigSkeleton +readProjectLocalConfigOrDefault verbosity httpTransport distDirLayout = do usesExplicitProjectRoot <- liftIO $ doesFileExist projectFile if usesExplicitProjectRoot then do - readProjectFile verbosity distDirLayout "" "project file" + readProjectFileSkeleton verbosity httpTransport distDirLayout "" "project file" else do monitorFiles [monitorNonExistentFile projectFile] - return defaultImplicitProjectConfig + return (singletonProjectConfigSkeleton defaultImplicitProjectConfig) where projectFile :: FilePath @@ -547,66 +548,43 @@ readProjectLocalConfigOrDefault verbosity distDirLayout = do -- or returns empty. This file gets written by @cabal configure@, or in -- principle can be edited manually or by other tools. -- -readProjectLocalExtraConfig :: Verbosity -> DistDirLayout - -> Rebuild ProjectConfig -readProjectLocalExtraConfig verbosity distDirLayout = - readProjectFile verbosity distDirLayout "local" +readProjectLocalExtraConfig :: Verbosity -> HttpTransport -> DistDirLayout + -> Rebuild ProjectConfigSkeleton +readProjectLocalExtraConfig verbosity httpTransport distDirLayout = + readProjectFileSkeleton verbosity httpTransport distDirLayout "local" "project local configuration file" -- | Reads a @cabal.project.freeze@ file in the given project root dir, -- or returns empty. This file gets written by @cabal freeze@, or in -- principle can be edited manually or by other tools. -- -readProjectLocalFreezeConfig :: Verbosity -> DistDirLayout - -> Rebuild ProjectConfig -readProjectLocalFreezeConfig verbosity distDirLayout = - readProjectFile verbosity distDirLayout "freeze" +readProjectLocalFreezeConfig :: Verbosity -> HttpTransport ->DistDirLayout + -> Rebuild ProjectConfigSkeleton +readProjectLocalFreezeConfig verbosity httpTransport distDirLayout = + readProjectFileSkeleton verbosity httpTransport distDirLayout "freeze" "project freeze file" --- | Reads a named config file in the given project root dir, or returns empty. +-- | Reads a named extended (with imports and conditionals) config file in the given project root dir, or returns empty. -- -readProjectFile :: Verbosity - -> DistDirLayout - -> String - -> String - -> Rebuild ProjectConfig -readProjectFile verbosity DistDirLayout{distProjectFile} +readProjectFileSkeleton :: Verbosity -> HttpTransport -> DistDirLayout -> String -> String -> Rebuild ProjectConfigSkeleton +readProjectFileSkeleton verbosity httpTransport DistDirLayout{distProjectFile, distDownloadSrcDirectory} extensionName extensionDescription = do exists <- liftIO $ doesFileExist extensionFile if exists then do monitorFiles [monitorFileHashed extensionFile] - addProjectFileProvenance <$> liftIO readExtensionFile + pcs <- liftIO readExtensionFile + monitorFiles $ map monitorFileHashed (projectSkeletonImports pcs) + pure pcs else do monitorFiles [monitorNonExistentFile extensionFile] return mempty where - extensionFile :: FilePath extensionFile = distProjectFile extensionName - readExtensionFile :: IO ProjectConfig readExtensionFile = reportParseResult verbosity extensionDescription extensionFile - . (parseProjectConfig extensionFile) + =<< parseProjectSkeleton distDownloadSrcDirectory httpTransport verbosity [] extensionFile =<< BS.readFile extensionFile - addProjectFileProvenance :: ProjectConfig -> ProjectConfig - addProjectFileProvenance config = - config { - projectConfigProvenance = - Set.insert (Explicit extensionFile) (projectConfigProvenance config) - } - - --- | Parse the 'ProjectConfig' format. --- --- For the moment this is implemented in terms of parsers for legacy --- configuration types, plus a conversion. --- -parseProjectConfig :: FilePath -> BS.ByteString -> OldParser.ParseResult ProjectConfig -parseProjectConfig source content = - convertLegacyProjectConfig <$> - (parseLegacyProjectConfig source content) - - -- | Render the 'ProjectConfig' format. -- -- For the moment this is implemented in terms of a pretty printer for the @@ -647,12 +625,12 @@ readGlobalConfig verbosity configFileFlag = do monitorFiles [monitorFileHashed configFile] return (convertLegacyGlobalConfig config) -reportParseResult :: Verbosity -> String -> FilePath -> OldParser.ParseResult a -> IO a +reportParseResult :: Verbosity -> String -> FilePath -> OldParser.ParseResult ProjectConfigSkeleton -> IO ProjectConfigSkeleton reportParseResult verbosity _filetype filename (OldParser.ParseOk warnings x) = do - unless (null warnings) $ - let msg = unlines (map (OldParser.showPWarning filename) warnings) + unless (null warnings) $ + let msg = unlines (map (OldParser.showPWarning (intercalate ", " $ filename : projectSkeletonImports x)) warnings) in warn verbosity msg - return x + return x reportParseResult verbosity filetype filename (OldParser.ParseFailed err) = let (line, msg) = OldParser.locatedErrorMsg err in die' verbosity $ "Error parsing " ++ filetype ++ " " ++ filename diff --git a/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs b/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs index dcf12ec9815..2ed03cc6888 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs @@ -1,9 +1,16 @@ -{-# LANGUAGE RecordWildCards, NamedFieldPuns, DeriveGeneric, ConstraintKinds #-} +{-# LANGUAGE RecordWildCards, NamedFieldPuns, DeriveGeneric, ConstraintKinds, FlexibleInstances #-} -- | Project configuration, implementation in terms of legacy types. -- module Distribution.Client.ProjectConfig.Legacy ( + -- Project config skeletons + ProjectConfigSkeleton, + parseProjectSkeleton, + instantiateProjectConfigSkeleton, + singletonProjectConfigSkeleton, + projectSkeletonImports, + -- * Project config in terms of legacy types LegacyProjectConfig, parseLegacyProjectConfig, @@ -17,13 +24,12 @@ module Distribution.Client.ProjectConfig.Legacy ( -- * Internals, just for tests parsePackageLocationTokenQ, - renderPackageLocationToken, + renderPackageLocationToken ) where -import Prelude () import Distribution.Client.Compat.Prelude -import Distribution.Types.Flag (parsecFlagAssignment) +import Distribution.Types.Flag (parsecFlagAssignment, FlagName) import Distribution.Client.ProjectConfig.Types import Distribution.Client.Types.RepoName (RepoName (..), unRepoName) @@ -38,18 +44,23 @@ import Distribution.Client.CmdInstall.ClientInstallFlags ( ClientInstallFlags(..), defaultClientInstallFlags , clientInstallOptions ) +import Distribution.Compat.Lens (view) + import Distribution.Solver.Types.ConstraintSource import Distribution.FieldGrammar import Distribution.Package import Distribution.Types.SourceRepo (RepoType) +import Distribution.Types.CondTree + ( CondTree (..), CondBranch (..), mapTreeConds, traverseCondTreeC ) import Distribution.PackageDescription - ( dispFlagAssignment ) + ( dispFlagAssignment, Condition (..), ConfVar (..), FlagAssignment ) +import Distribution.PackageDescription.Configuration (simplifyWithSysParams) import Distribution.Simple.Compiler - ( OptimisationLevel(..), DebugInfoLevel(..) ) + ( OptimisationLevel(..), DebugInfoLevel(..), CompilerInfo(..) ) import Distribution.Simple.InstallDirs ( CopyDest (NoCopyDest) ) import Distribution.Simple.Setup - ( Flag(Flag), toFlag, fromFlagOrDefault + ( Flag(..), toFlag, fromFlagOrDefault , ConfigFlags(..), configureOptions , HaddockFlags(..), haddockOptions, defaultHaddockFlags , TestFlags(..), testOptions', defaultTestFlags @@ -85,7 +96,7 @@ import Distribution.Deprecated.ParseUtils ( ParseResult(..), PError(..), syntaxError, PWarning(..) , commaNewLineListFieldParsec, newLineListField, parseTokenQ , parseHaskellString, showToken - , simpleFieldParsec + , simpleFieldParsec, parseFail ) import Distribution.Client.ParseUtils import Distribution.Simple.Command @@ -94,11 +105,130 @@ import Distribution.Simple.Command import Distribution.Types.PackageVersionConstraint ( PackageVersionConstraint ) import Distribution.Parsec (ParsecParser, parsecToken) +import Distribution.System (OS, Arch) import qualified Data.Map as Map -import qualified Data.ByteString as BS +import qualified Data.Set as Set +import qualified Data.ByteString.Char8 as BS + +import Network.URI (URI (..), parseURI) + +import Distribution.Fields.ConfVar (parseConditionConfVarFromClause) + +import Distribution.Client.HttpUtils +import System.FilePath ((), isPathSeparator, makeValid) +import System.Directory (createDirectoryIfMissing) + + + + +------------------------------------------------------------------ +-- Handle extended project config files with conditionals and imports. +-- + +-- | ProjectConfigSkeleton is a tree of conditional blocks and imports wrapping a config. It can be finalized by providing the conditional resolution info +-- and then resolving and downloading the imports +type ProjectConfigSkeleton = CondTree ConfVar [ProjectConfigImport] ProjectConfig +type ProjectConfigImport = String + + +singletonProjectConfigSkeleton :: ProjectConfig -> ProjectConfigSkeleton +singletonProjectConfigSkeleton x = CondNode x mempty mempty + +instantiateProjectConfigSkeleton :: OS -> Arch -> CompilerInfo -> FlagAssignment -> ProjectConfigSkeleton -> ProjectConfig +instantiateProjectConfigSkeleton os arch impl _flags skel = go $ mapTreeConds (fst . simplifyWithSysParams os arch impl) skel + where + go :: CondTree + FlagName + [ProjectConfigImport] + ProjectConfig + -> ProjectConfig + go (CondNode l _imps ts) = + let branches = concatMap processBranch ts + in l <> mconcat branches + processBranch (CondBranch cnd t mf) = case cnd of + (Lit True) -> [go t] + (Lit False) -> maybe ([]) ((:[]) . go) mf + _ -> error $ "unable to process condition: " ++ show cnd -- TODO it would be nice if there were a pretty printer + +projectSkeletonImports :: ProjectConfigSkeleton -> [ProjectConfigImport] +projectSkeletonImports = view traverseCondTreeC + +parseProjectSkeleton :: FilePath -> HttpTransport -> Verbosity -> [ProjectConfigImport] -> FilePath -> BS.ByteString -> IO (ParseResult ProjectConfigSkeleton) +parseProjectSkeleton cacheDir httpTransport verbosity seenImports source bs = (sanityWalkPCS False =<<) <$> liftPR (go []) (ParseUtils.readFields bs) + where + go :: [ParseUtils.Field] -> [ParseUtils.Field] -> IO (ParseResult ProjectConfigSkeleton) + go acc (x:xs) = case x of + (ParseUtils.F l "import" importLoc) -> + if importLoc `elem` seenImports + then pure . parseFail $ ParseUtils.FromString ("cyclical import of " ++ importLoc) (Just l) + else do + let fs = fmap (\z -> CondNode z [importLoc] mempty) $ fieldsToConfig (reverse acc) + res <- parseProjectSkeleton cacheDir httpTransport verbosity (importLoc : seenImports) importLoc =<< fetchImportConfig importLoc + rest <- go [] xs + pure . fmap mconcat . sequence $ [fs, res, rest] + (ParseUtils.Section l "if" p xs') -> do + subpcs <- go [] xs' + let fs = fmap singletonProjectConfigSkeleton $ fieldsToConfig (reverse acc) + (elseClauses, rest) <- parseElseClauses xs + let condNode = (\c pcs e -> CondNode mempty mempty [CondBranch c pcs e]) <$> + -- we rewrap as as a section so the readFields lexer of the conditional parser doesn't get confused + adaptParseError l (parseConditionConfVarFromClause . BS.pack $ "if(" <> p <> ")") <*> + subpcs <*> + elseClauses + pure . fmap mconcat . sequence $ [fs, condNode, rest] + _ -> go (x:acc) xs + go acc [] = pure . fmap singletonProjectConfigSkeleton . fieldsToConfig $ reverse acc + + parseElseClauses :: [ParseUtils.Field] -> IO (ParseResult (Maybe ProjectConfigSkeleton), ParseResult ProjectConfigSkeleton) + parseElseClauses x = case x of + (ParseUtils.Section _l "else" _p xs':xs) -> do + subpcs <- go [] xs' + rest <- go [] xs + pure (Just <$> subpcs, rest) + (ParseUtils.Section l "elif" p xs':xs) -> do + subpcs <- go [] xs' + (elseClauses, rest) <- parseElseClauses xs + let condNode = (\c pcs e -> CondNode mempty mempty [CondBranch c pcs e]) <$> + adaptParseError l (parseConditionConfVarFromClause . BS.pack $ "else("<> p <> ")") <*> + subpcs <*> + elseClauses + pure (Just <$> condNode, rest) + _ -> (\r -> (pure Nothing,r)) <$> go [] x + + fieldsToConfig xs = fmap (addProvenance . convertLegacyProjectConfig) $ parseLegacyProjectConfigFields source xs + addProvenance x = x {projectConfigProvenance = Set.singleton (Explicit source)} + + adaptParseError _ (Right x) = pure x + adaptParseError l (Left e) = parseFail $ ParseUtils.FromString (show e) (Just l) + + liftPR :: (a -> IO (ParseResult b)) -> ParseResult a -> IO (ParseResult b) + liftPR f (ParseOk ws x) = addWarnings <$> f x + where addWarnings (ParseOk ws' x') = ParseOk (ws' ++ ws) x' + addWarnings x' = x' + liftPR _ (ParseFailed e) = pure $ ParseFailed e + + fetchImportConfig :: ProjectConfigImport -> IO BS.ByteString + fetchImportConfig pci = case parseURI pci of + Just uri -> do + let fp = cacheDir map (\x -> if isPathSeparator x then '_' else x) (makeValid $ show uri) + createDirectoryIfMissing True cacheDir + _ <- downloadURI httpTransport verbosity uri fp + BS.readFile fp + Nothing -> BS.readFile pci + + modifiesCompiler :: ProjectConfig -> Bool + modifiesCompiler pc = isSet projectConfigHcFlavor || isSet projectConfigHcPath || isSet projectConfigHcPkg + where + isSet f = f (projectConfigShared pc) /= NoFlag -import Network.URI (URI (..)) + sanityWalkPCS :: Bool -> ProjectConfigSkeleton -> ParseResult ProjectConfigSkeleton + sanityWalkPCS underConditional t@(CondNode d _c comps) + | underConditional && modifiesCompiler d = parseFail $ ParseUtils.FromString "Cannot set compiler in a conditional clause of a cabal project file" Nothing + | otherwise = mapM_ sanityWalkBranch comps >> pure t + + sanityWalkBranch:: CondBranch ConfVar [ProjectConfigImport] ProjectConfig -> ParseResult () + sanityWalkBranch (CondBranch _c t f) = traverse (sanityWalkPCS True) f >> sanityWalkPCS True t >> pure () ------------------------------------------------------------------ -- Representing the project config file in terms of legacy types @@ -851,15 +981,18 @@ convertToLegacyPerPackageConfig PackageConfig {..} = -- Parsing and showing the project config file -- -parseLegacyProjectConfig :: FilePath -> BS.ByteString -> ParseResult LegacyProjectConfig -parseLegacyProjectConfig source = - parseConfig (legacyProjectConfigFieldDescrs constraintSrc) +parseLegacyProjectConfigFields :: FilePath -> [ParseUtils.Field] -> ParseResult LegacyProjectConfig +parseLegacyProjectConfigFields source = + parseFieldsAndSections (legacyProjectConfigFieldDescrs constraintSrc) legacyPackageConfigSectionDescrs legacyPackageConfigFGSectionDescrs mempty where constraintSrc = ConstraintSourceProjectConfig source +parseLegacyProjectConfig :: FilePath -> BS.ByteString -> ParseResult LegacyProjectConfig +parseLegacyProjectConfig source bs = parseLegacyProjectConfigFields source =<< ParseUtils.readFields bs + showLegacyProjectConfig :: LegacyProjectConfig -> String showLegacyProjectConfig config = Disp.render $ diff --git a/cabal-install/src/Distribution/Client/ProjectOrchestration.hs b/cabal-install/src/Distribution/Client/ProjectOrchestration.hs index d4de014a7de..d7933b6c2a9 100644 --- a/cabal-install/src/Distribution/Client/ProjectOrchestration.hs +++ b/cabal-install/src/Distribution/Client/ProjectOrchestration.hs @@ -139,6 +139,7 @@ import qualified Distribution.Client.BuildReports.Storage as BuildReports ( storeLocal ) import Distribution.Client.Config (getCabalDir) +import Distribution.Client.HttpUtils import Distribution.Client.Setup hiding (packageName) import Distribution.Compiler ( CompilerFlavor(GHC) ) @@ -170,7 +171,8 @@ import Distribution.Version import Distribution.Simple.Compiler ( compilerCompatVersion, showCompilerId, compilerId, compilerInfo , OptimisationLevel(..)) - +import Distribution.Utils.NubList + ( fromNubList ) import Distribution.System ( Platform(Platform) ) @@ -210,7 +212,7 @@ establishProjectBaseContext verbosity cliConfig currentCommand = do establishProjectBaseContextWithRoot verbosity cliConfig projectRoot currentCommand where mprojectFile = Setup.flagToMaybe projectConfigProjectFile - ProjectConfigShared { projectConfigProjectFile } = projectConfigShared cliConfig + ProjectConfigShared { projectConfigProjectFile} = projectConfigShared cliConfig -- | Like 'establishProjectBaseContext' but doesn't search for project root. establishProjectBaseContextWithRoot @@ -224,8 +226,13 @@ establishProjectBaseContextWithRoot verbosity cliConfig projectRoot currentComma let distDirLayout = defaultDistDirLayout projectRoot mdistDirectory + httpTransport <- configureTransport verbosity + (fromNubList . projectConfigProgPathExtra $ projectConfigShared cliConfig) + (flagToMaybe . projectConfigHttpTransport $ projectConfigBuildOnly cliConfig) + (projectConfig, localPackages) <- rebuildProjectConfig verbosity + httpTransport distDirLayout cliConfig diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 6ed918bb9c7..7d4ec4186f7 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -40,6 +40,7 @@ module Distribution.Client.ProjectPlanning ( -- * Utils required for building pkgHasEphemeralBuildTargets, elabBuildTargetWholeComponents, + configureCompiler, -- * Setup.hs CLI flags for building setupHsScriptOptions, @@ -71,11 +72,13 @@ import Prelude () import Distribution.Client.Compat.Prelude import Distribution.Client.HashValue +import Distribution.Client.HttpUtils import Distribution.Client.ProjectPlanning.Types as Ty import Distribution.Client.PackageHash import Distribution.Client.RebuildMonad import Distribution.Client.Store import Distribution.Client.ProjectConfig +import Distribution.Client.ProjectConfig.Legacy import Distribution.Client.ProjectPlanOutput import Distribution.Client.Types @@ -300,11 +303,13 @@ sanityCheckElaboratedPackage ElaboratedConfiguredPackage{..} -- packages within the project. -- rebuildProjectConfig :: Verbosity + -> HttpTransport -> DistDirLayout -> ProjectConfig -> IO ( ProjectConfig , [PackageSpecifier UnresolvedSourcePackage] ) rebuildProjectConfig verbosity + httpTransport distDirLayout@DistDirLayout { distProjectRootDirectory, distDirectory, @@ -322,10 +327,14 @@ rebuildProjectConfig verbosity runRebuild distProjectRootDirectory $ rerunIfChanged verbosity fileMonitorProjectConfig - fileMonitorProjectConfigKey + fileMonitorProjectConfigKey -- todo check deps too? $ do liftIO $ info verbosity "Project settings changed, reconfiguring..." - projectConfig <- phaseReadProjectConfig + liftIO $ createDirectoryIfMissingVerbose verbosity True distProjectCacheDirectory + projectConfigSkeleton <- phaseReadProjectConfig + -- have to create the cache directory before configuring the compiler + (compiler, Platform arch os, _) <- configureCompiler verbosity distDirLayout ((fst $ PD.ignoreConditions projectConfigSkeleton) <> cliConfig) + let projectConfig = instantiateProjectConfigSkeleton os arch (compilerInfo compiler) mempty projectConfigSkeleton localPackages <- phaseReadLocalPackages (projectConfig <> cliConfig) return (projectConfig, localPackages) @@ -353,9 +362,9 @@ rebuildProjectConfig verbosity -- Read the cabal.project (or implicit config) and combine it with -- arguments from the command line -- - phaseReadProjectConfig :: Rebuild ProjectConfig + phaseReadProjectConfig :: Rebuild ProjectConfigSkeleton phaseReadProjectConfig = do - readProjectConfig verbosity projectConfigConfigFile distDirLayout + readProjectConfig verbosity httpTransport projectConfigConfigFile distDirLayout -- Look for all the cabal packages in the project -- some of which may be local src dirs, tarballs etc @@ -380,6 +389,60 @@ rebuildProjectConfig verbosity pkgLocations +configureCompiler :: Verbosity -> + DistDirLayout -> + ProjectConfig -> + Rebuild (Compiler, Platform, ProgramDb) +configureCompiler verbosity + DistDirLayout { + distProjectCacheFile + } + ProjectConfig { + projectConfigShared = ProjectConfigShared { + projectConfigHcFlavor, + projectConfigHcPath, + projectConfigHcPkg + }, + projectConfigLocalPackages = PackageConfig { + packageConfigProgramPaths, + packageConfigProgramPathExtra + } + } = do + let fileMonitorCompiler = newFileMonitor . distProjectCacheFile $ "compiler" + + progsearchpath <- liftIO $ getSystemSearchPath + rerunIfChanged verbosity fileMonitorCompiler + (hcFlavor, hcPath, hcPkg, progsearchpath, + packageConfigProgramPaths, + packageConfigProgramPathExtra) $ do + + liftIO $ info verbosity "Compiler settings changed, reconfiguring..." + result@(_, _, progdb') <- liftIO $ + Cabal.configCompilerEx + hcFlavor hcPath hcPkg + progdb verbosity + + -- Note that we added the user-supplied program locations and args + -- for /all/ programs, not just those for the compiler prog and + -- compiler-related utils. In principle we don't know which programs + -- the compiler will configure (and it does vary between compilers). + -- We do know however that the compiler will only configure the + -- programs it cares about, and those are the ones we monitor here. + monitorFiles (programsMonitorFiles progdb') + + return result + where + hcFlavor = flagToMaybe projectConfigHcFlavor + hcPath = flagToMaybe projectConfigHcPath + hcPkg = flagToMaybe projectConfigHcPkg + progdb = + userSpecifyPaths (Map.toList (getMapLast packageConfigProgramPaths)) + . modifyProgramSearchPath + (++ [ ProgramSearchPathDir dir + | dir <- fromNubList packageConfigProgramPathExtra ]) + $ defaultProgramDb + + -- | Return an up-to-date elaborated install plan. -- -- Two variants of the install plan are returned: with and without packages @@ -451,7 +514,6 @@ rebuildInstallPlan verbosity return (improvedPlan, elaboratedPlan, elaboratedShared, totalIndexState, activeRepos) where - fileMonitorCompiler = newFileMonitorInCacheDir "compiler" fileMonitorSolverPlan = newFileMonitorInCacheDir "solver-plan" fileMonitorSourceHashes = newFileMonitorInCacheDir "source-hashes" fileMonitorElaboratedPlan = newFileMonitorInCacheDir "elaborated-plan" @@ -468,49 +530,7 @@ rebuildInstallPlan verbosity -- phaseConfigureCompiler :: ProjectConfig -> Rebuild (Compiler, Platform, ProgramDb) - phaseConfigureCompiler ProjectConfig { - projectConfigShared = ProjectConfigShared { - projectConfigHcFlavor, - projectConfigHcPath, - projectConfigHcPkg - }, - projectConfigLocalPackages = PackageConfig { - packageConfigProgramPaths, - packageConfigProgramPathExtra - } - } = do - progsearchpath <- liftIO $ getSystemSearchPath - rerunIfChanged verbosity fileMonitorCompiler - (hcFlavor, hcPath, hcPkg, progsearchpath, - packageConfigProgramPaths, - packageConfigProgramPathExtra) $ do - - liftIO $ info verbosity "Compiler settings changed, reconfiguring..." - result@(_, _, progdb') <- liftIO $ - Cabal.configCompilerEx - hcFlavor hcPath hcPkg - progdb verbosity - - -- Note that we added the user-supplied program locations and args - -- for /all/ programs, not just those for the compiler prog and - -- compiler-related utils. In principle we don't know which programs - -- the compiler will configure (and it does vary between compilers). - -- We do know however that the compiler will only configure the - -- programs it cares about, and those are the ones we monitor here. - monitorFiles (programsMonitorFiles progdb') - - return result - where - hcFlavor = flagToMaybe projectConfigHcFlavor - hcPath = flagToMaybe projectConfigHcPath - hcPkg = flagToMaybe projectConfigHcPkg - progdb = - userSpecifyPaths (Map.toList (getMapLast packageConfigProgramPaths)) - . modifyProgramSearchPath - (++ [ ProgramSearchPathDir dir - | dir <- fromNubList packageConfigProgramPathExtra ]) - $ defaultProgramDb - + phaseConfigureCompiler = configureCompiler verbosity distDirLayout -- Configuring other programs. -- diff --git a/cabal-install/src/Distribution/Client/ScriptUtils.hs b/cabal-install/src/Distribution/Client/ScriptUtils.hs index 9f012380256..6555b92ef7c 100644 --- a/cabal-install/src/Distribution/Client/ScriptUtils.hs +++ b/cabal-install/src/Distribution/Client/ScriptUtils.hs @@ -27,13 +27,21 @@ import Distribution.Client.DistDirLayout ( DistDirLayout(..) ) import Distribution.Client.HashValue ( hashValue, showHashValue ) +import Distribution.Client.HttpUtils + ( HttpTransport, configureTransport ) import Distribution.Client.NixStyleOptions ( NixStyleFlags (..) ) import Distribution.Client.ProjectConfig ( ProjectConfig(..), ProjectConfigShared(..) - , parseProjectConfig, reportParseResult, withProjectOrGlobalConfig ) + , reportParseResult, withProjectOrGlobalConfig + , projectConfigHttpTransport ) +import Distribution.Client.ProjectConfig.Legacy + ( ProjectConfigSkeleton + , parseProjectSkeleton, instantiateProjectConfigSkeleton ) import Distribution.Client.ProjectFlags ( flagIgnoreProject ) +import Distribution.Client.RebuildMonad + ( runRebuild ) import Distribution.Client.Setup ( ConfigFlags(..), GlobalFlags(..) ) import Distribution.Client.TargetSelector @@ -44,6 +52,8 @@ import Distribution.FieldGrammar ( parseFieldGrammar, takeFields ) import Distribution.Fields ( ParseResult, parseFatalFailure, readFields ) +import Distribution.PackageDescription + ( ignoreConditions ) import Distribution.PackageDescription.FieldGrammar ( executableFieldGrammar ) import Distribution.PackageDescription.PrettyPrint @@ -51,16 +61,20 @@ import Distribution.PackageDescription.PrettyPrint import Distribution.Parsec ( Position(..) ) import Distribution.Simple.Flag - ( fromFlagOrDefault ) + ( fromFlagOrDefault, flagToMaybe ) import Distribution.Simple.PackageDescription ( parseString ) import Distribution.Simple.Setup ( Flag(..) ) +import Distribution.Simple.Compiler + ( compilerInfo ) import Distribution.Simple.Utils ( createDirectoryIfMissingVerbose, createTempDirectory, die', handleDoesNotExist, readUTF8File, warn, writeUTF8File ) import qualified Distribution.SPDX.License as SPDX import Distribution.Solver.Types.SourcePackage as SP ( SourcePackage(..) ) +import Distribution.System + ( Platform(..) ) import Distribution.Types.BuildInfo ( BuildInfo(..) ) import Distribution.Types.CondTree @@ -73,6 +87,10 @@ import Distribution.Types.PackageDescription ( PackageDescription(..), emptyPackageDescription ) import Distribution.Types.PackageName.Magic ( fakePackageCabalFileName, fakePackageId ) +import Distribution.Utils.NubList + ( fromNubList ) +import Distribution.Client.ProjectPlanning + ( configureCompiler ) import Distribution.Verbosity ( normal ) import Language.Haskell.Extension @@ -217,7 +235,17 @@ withContextAndSelectors noTargets kind flags@NixStyleFlags {..} targetStrings gl scriptContents <- BS.readFile script executable <- readExecutableBlockFromScript verbosity scriptContents - projectCfg <- readProjectBlockFromScript verbosity (takeFileName script) scriptContents + + + httpTransport <- configureTransport verbosity + (fromNubList . projectConfigProgPathExtra $ projectConfigShared cliConfig) + (flagToMaybe . projectConfigHttpTransport $ projectConfigBuildOnly cliConfig) + + projectCfgSkeleton <- readProjectBlockFromScript verbosity httpTransport (distDirLayout ctx) (takeFileName script) scriptContents + + (compiler, Platform arch os, _) <- runRebuild (distProjectRootDirectory . distDirLayout $ ctx) $ configureCompiler verbosity (distDirLayout ctx) ((fst $ ignoreConditions projectCfgSkeleton) <> projectConfig ctx) + + let projectCfg = instantiateProjectConfigSkeleton os arch (compilerInfo compiler) mempty projectCfgSkeleton :: ProjectConfig let executable' = executable & L.buildInfo . L.defaultLanguage %~ maybe (Just Haskell2010) Just ctx' = ctx & lProjectConfig %~ (<> projectCfg) @@ -312,12 +340,12 @@ readExecutableBlockFromScript verbosity str = do -- * @-}@ -- -- Return the metadata. -readProjectBlockFromScript :: Verbosity -> String -> BS.ByteString -> IO ProjectConfig -readProjectBlockFromScript verbosity scriptName str = do +readProjectBlockFromScript :: Verbosity -> HttpTransport -> DistDirLayout -> String -> BS.ByteString -> IO ProjectConfigSkeleton +readProjectBlockFromScript verbosity httpTransport DistDirLayout{distDownloadSrcDirectory} scriptName str = do case extractScriptBlock "project" str of Left _ -> return mempty - Right x -> reportParseResult verbosity "script" scriptName - $ parseProjectConfig scriptName x + Right x -> reportParseResult verbosity "script" scriptName + =<< parseProjectSkeleton distDownloadSrcDirectory httpTransport verbosity [] scriptName x -- | Extract the first encountered script metadata block started end -- terminated by the tokens diff --git a/cabal-install/src/Distribution/Deprecated/ParseUtils.hs b/cabal-install/src/Distribution/Deprecated/ParseUtils.hs index 78f57b58d78..6ac62a6e82d 100644 --- a/cabal-install/src/Distribution/Deprecated/ParseUtils.hs +++ b/cabal-install/src/Distribution/Deprecated/ParseUtils.hs @@ -105,13 +105,21 @@ instance Monad ParseResult where ParseOk ws x >>= f = case f x of ParseFailed err -> ParseFailed err ParseOk ws' x' -> ParseOk (ws'++ws) x' - #if !(MIN_VERSION_base(4,9,0)) fail = parseResultFail #elif !(MIN_VERSION_base(4,13,0)) fail = Fail.fail #endif +instance Foldable ParseResult where + foldMap _ (ParseFailed _ ) = mempty + foldMap f (ParseOk _ x) = f x + +instance Traversable ParseResult where + traverse _ (ParseFailed err) = pure (ParseFailed err) + traverse f (ParseOk ws x) = ParseOk ws <$> f x + + instance Fail.MonadFail ParseResult where fail = parseResultFail diff --git a/cabal-install/tests/IntegrationTests2.hs b/cabal-install/tests/IntegrationTests2.hs index 57b21bc3b10..94fbac61578 100644 --- a/cabal-install/tests/IntegrationTests2.hs +++ b/cabal-install/tests/IntegrationTests2.hs @@ -16,6 +16,7 @@ import Prelude () import Distribution.Client.DistDirLayout import Distribution.Client.ProjectConfig import Distribution.Client.Config (getCabalDir) +import Distribution.Client.HttpUtils import Distribution.Client.TargetSelector hiding (DirActions(..)) import qualified Distribution.Client.TargetSelector as TS (DirActions(..)) import Distribution.Client.ProjectPlanning @@ -1690,8 +1691,11 @@ configureProject testdir cliConfig = do -- ended in an exception (as we leave the files to help with debugging). cleanProject testdir + httpTransport <- configureTransport verbosity [] Nothing + (projectConfig, localPackages) <- rebuildProjectConfig verbosity + httpTransport distDirLayout cliConfig diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/Foo.hs b/cabal-testsuite/PackageTests/ConditionalAndImport/Foo.hs new file mode 100644 index 00000000000..efbf93bbde8 --- /dev/null +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/Foo.hs @@ -0,0 +1 @@ +module Foo where diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/cabal-bad-conditional.project b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal-bad-conditional.project new file mode 100644 index 00000000000..1cd137e6de7 --- /dev/null +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal-bad-conditional.project @@ -0,0 +1,4 @@ +packages: . + +if(True) + compiler: ghc diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/cabal-cyclical.project b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal-cyclical.project new file mode 100644 index 00000000000..db226311abc --- /dev/null +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal-cyclical.project @@ -0,0 +1,3 @@ +packages: . + +import: cabal-cyclical.project diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.out b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.out new file mode 100644 index 00000000000..e125635e0eb --- /dev/null +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.out @@ -0,0 +1,18 @@ +# cabal v2-update +Downloading the latest package list from test-local-repo +# cabal v2-run +Resolving dependencies... +Build profile: -w ghc- -O1 +In order, the following will be built: + - some-exe-0.0.1.0 (exe:some-exe) (requires build) +Configuring some-exe-0.0.1.0... +Preprocessing executable 'some-exe' for some-exe-0.0.1.0.. +Building executable 'some-exe' for some-exe-0.0.1.0.. +Installing executable some-exe in +Warning: The directory /cabal.dist/home/.cabal/store/ghc-/incoming/new-/cabal.dist/home/.cabal/store/ghc-/-/bin is not in the system search path. +# cabal v2-build +Error: cabal: Error parsing project file /cabal-cyclical.project:3: +cyclical import of cabal-cyclical.project +# cabal v2-build +Error: cabal: Error parsing project file /cabal-bad-conditional.project: +Cannot set compiler in a conditional clause of a cabal project file diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.project b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.project new file mode 100644 index 00000000000..0aeed82370c --- /dev/null +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.project @@ -0,0 +1,3 @@ +packages: . + +import: extra.project diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.test.hs b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.test.hs new file mode 100644 index 00000000000..0791050f66d --- /dev/null +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.test.hs @@ -0,0 +1,6 @@ +import Test.Cabal.Prelude +main = cabalTest $ + withRepo "repo" $ do + cabal "v2-run" [ "some-exe" ] + fails $ cabal "v2-build" [ "--project=cabal-cyclical.project" ] + fails $ cabal "v2-build" [ "--project=cabal-bad-conditional.project" ] diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/extra.project b/cabal-testsuite/PackageTests/ConditionalAndImport/extra.project new file mode 100644 index 00000000000..52a1f847fbb --- /dev/null +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/extra.project @@ -0,0 +1,8 @@ +if(os(NoSuchOs) || False) + extra-packages: no-such-package +elif(os(Whoops)) + extra-packages: no-can-do +elif(True) + extra-packages: some-exe +else + extra-packages: nope diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/my.cabal b/cabal-testsuite/PackageTests/ConditionalAndImport/my.cabal new file mode 100644 index 00000000000..b1b36c1e620 --- /dev/null +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/my.cabal @@ -0,0 +1,9 @@ +name: my +version: 0.1 +license: BSD3 +cabal-version: >= 1.2 +build-type: Simple + +library + exposed-modules: Foo + build-depends: base diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/repo/some-exe-0.0.1.0/Main.hs b/cabal-testsuite/PackageTests/ConditionalAndImport/repo/some-exe-0.0.1.0/Main.hs new file mode 100644 index 00000000000..33581fa8421 --- /dev/null +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/repo/some-exe-0.0.1.0/Main.hs @@ -0,0 +1,4 @@ +module Main where + +main :: IO () +main = putStrLn "hello world" diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/repo/some-exe-0.0.1.0/some-exe.cabal b/cabal-testsuite/PackageTests/ConditionalAndImport/repo/some-exe-0.0.1.0/some-exe.cabal new file mode 100644 index 00000000000..3a2e620d96e --- /dev/null +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/repo/some-exe-0.0.1.0/some-exe.cabal @@ -0,0 +1,9 @@ +name: some-exe +version: 0.0.1.0 +license: BSD3 +cabal-version: >= 1.2 +build-type: Simple + +Executable some-exe + main-is: Main.hs + build-depends: base diff --git a/changelog.d/pr-7783 b/changelog.d/pr-7783 new file mode 100644 index 00000000000..bbf8cda6f8d --- /dev/null +++ b/changelog.d/pr-7783 @@ -0,0 +1,11 @@ +synopsis: Conditionals and imports in cabal.project files +packages: cabal-install +prs: #7783 +issues: #7556 +significance: significant + +description: { + +Cabal.project files now allow conditional logic on compiler version, arch, etc. as well as imports of other local or remote project of freeze files (both old and new style). + +} diff --git a/doc/cabal-package.rst b/doc/cabal-package.rst index 2cad579ea04..8dc0568a4e8 100644 --- a/doc/cabal-package.rst +++ b/doc/cabal-package.rst @@ -2581,6 +2581,8 @@ Since Cabal 2.2 conditional blocks support ``elif`` construct. else property-descriptions-or-conditionals +.. _conditions: + Conditions """""""""" diff --git a/doc/cabal-project.rst b/doc/cabal-project.rst index 988572e0a3d..52322a04b51 100644 --- a/doc/cabal-project.rst +++ b/doc/cabal-project.rst @@ -35,6 +35,32 @@ options): Any call to ``cabal build`` will consider ``cabal.project*`` files from parent directories when there is none in the current directory. +Conditionals and imports +------------------------ + +As of ``cabal-install`` version 3.8, cabal supports conditional logic +and imports in ``cabal.project`` files. :ref:`conditions` in cabal +may case on operating system, architecture, and +compiler (i.e. there is no support for a notion of custom flags in +project files). Imports may specify local filepaths or remote urls, +and may reference either cabal.project files or v1-style cabal.config +freeze files. As a usage example: + +:: + + if(os(darwin)) + optimization: False + elif(os(freebsd)) + packages: freebsd/*.cabal + else + optimization: True + + import: https://some.remote.source/subdir/cabal.config + + import: relativepath/extra-project.project + + import: /absolutepath/some-project.project + Specifying the local packages -----------------------------