diff --git a/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs b/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs index 68c7346208b..af377bc6f0a 100644 --- a/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs +++ b/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs @@ -5,6 +5,8 @@ module Distribution.Solver.Types.ProjectConfigPath ( -- * Project Config Path Manipulation ProjectConfigPath(..) + , compareLexicographically + , compareSegmentally , projectConfigPathRoot , nullProjectConfigPath , consProjectConfigPath @@ -14,9 +16,6 @@ module Distribution.Solver.Types.ProjectConfigPath -- * Messages , docProjectConfigPath , docProjectImportedBy - , docProjectConfigFiles - , cyclicalImportMsg - , untrimmedUriImportMsg , docProjectConfigPathFailReason , quoteUntrimmed @@ -44,7 +43,6 @@ import Distribution.Solver.Modular.Version (VR) import Distribution.Pretty (prettyShow, Pretty(..)) import Distribution.Utils.String (trim) import Text.PrettyPrint -import Distribution.Simple.Utils (ordNub) import Distribution.System (OS(Windows), buildOS) -- | Path to a configuration file, either a singleton project root, or a longer @@ -59,10 +57,10 @@ import Distribution.System (OS(Windows), buildOS) -- List elements are relative to each other but once canonicalized, elements are -- relative to the directory of the project root. newtype ProjectConfigPath = ProjectConfigPath (NonEmpty FilePath) - deriving (Eq, Show, Generic) + deriving (Eq, Generic) -instance Pretty ProjectConfigPath where - pretty = docProjectConfigPath +instance Pretty ProjectConfigPath where pretty = docProjectConfigPath +instance Show ProjectConfigPath where show = prettyShow -- | Sorts URIs after local file paths and longer file paths after shorter ones -- as measured by the number of path segments. If still equal, then sorting is @@ -79,43 +77,91 @@ instance Pretty ProjectConfigPath where -- >>> let abBwd = ProjectConfigPath $ "a\\b.config" :| [] -- >>> compare abFwd abBwd -- EQ +-- +-- >>> let abc = ProjectConfigPath $ "a/b/c.config" :| [] +-- >>> let yz = ProjectConfigPath $ "y/z.config" :| [] +-- >>> (compare abc yz, let xs = [abc, yz] in xs == sort xs) +-- (GT,False) +-- +-- >>> let abc = ProjectConfigPath $ "C.config" :| ["B.config", "A.project"] +-- >>> let bcd = ProjectConfigPath $ "D.config" :| ["C.config", "B.project"] +-- >>> (compare abc bcd, let xs = [abc, bcd] in xs == sort xs) +-- (LT,True) +-- +-- >>> let abc = ProjectConfigPath $ "C.config" :| ["B.config", "A.project"] +-- >>> let yz = ProjectConfigPath $ "Z.config" :| ["Y.project"] +-- >>> (compare abc yz, let xs = [abc, yz] in xs == sort xs) +-- (GT,False) instance Ord ProjectConfigPath where - compare pa@(ProjectConfigPath (NE.toList -> as)) pb@(ProjectConfigPath (NE.toList -> bs)) = + compare = compareSegmentally + +-- | A comparison that puts projects first, URLs last and sorts the other paths +-- lexically. +compareLexicographically :: ProjectConfigPath -> ProjectConfigPath -> Ordering +compareLexicographically (ProjectConfigPath as) (ProjectConfigPath bs) = + case (as, bs) of + -- Single element paths are projects, they should always sort first. + (a :| [], b :| []) -> compare (splitPath a) (splitPath b) + (_ :| [], _) -> LT + (_, _ :| []) -> GT + + (a :| aImporters, b :| bImporters) -> case (parseAbsoluteURI a, parseAbsoluteURI b) of + (Just ua, Just ub) -> compare ua ub P.<> compare aImporters bImporters + (Just _, Nothing) -> GT + (Nothing, Just _) -> LT + (Nothing, Nothing) -> compare (splitPath a) (splitPath b) P.<> compare aImporters bImporters + +-- | A comparison that puts projects first, URLs last and sorts the other paths +-- by putting longer paths after shorter ones as measured by the number of path +-- segments. If still equal, then sorting is lexical. +compareSegmentally:: ProjectConfigPath -> ProjectConfigPath -> Ordering +compareSegmentally pa@(ProjectConfigPath as) pb@(ProjectConfigPath bs) = case (as, bs) of -- There should only ever be one root project path, only one path -- with length 1. Comparing it to itself should be EQ. Don't assume -- this though, do a comparison anyway when both sides have length -- 1. The root path, the project itself, should always be the first -- path in a sorted listing. - ([a], [b]) -> compare (splitPath a) (splitPath b) - ([_], _) -> LT - (_, [_]) -> GT + (a :| [], b :| []) -> + let aPaths = splitPath a + bPaths = splitPath b + in + compare (length aPaths) (length bPaths) + P.<> compare aPaths bPaths + + (_ :| [], _) -> LT + (_, _ :| []) -> GT - (a:_, b:_) -> case (parseAbsoluteURI a, parseAbsoluteURI b) of + (a :| _, b :| _) -> case (parseAbsoluteURI a, parseAbsoluteURI b) of (Just ua, Just ub) -> compare ua ub P.<> compare aImporters bImporters (Just _, Nothing) -> GT (Nothing, Just _) -> LT - (Nothing, Nothing) -> compare (splitPath a) (splitPath b) P.<> compare aImporters bImporters - _ -> - compare (length as) (length bs) - P.<> compare (length aPaths) (length bPaths) - P.<> compare aPaths bPaths + (Nothing, Nothing) -> + let aPaths = splitPath a + bPaths = splitPath b + in + compare (length as) (length bs) + P.<> compare asPaths bsPaths + P.<> compare (length aPaths) (length bPaths) + P.<> compare aPaths bPaths + P.<> compare aImporters bImporters where - splitPath = FP.splitPath . normSep where - normSep p = - if buildOS == Windows - then - Windows.joinPath $ Windows.splitDirectories - [if Posix.isPathSeparator c then Windows.pathSeparator else c| c <- p] - else - Posix.joinPath $ Posix.splitDirectories - [if Windows.isPathSeparator c then Posix.pathSeparator else c| c <- p] - - aPaths = splitPath <$> as - bPaths = splitPath <$> bs + asPaths = splitPath <$> as + bsPaths = splitPath <$> bs aImporters = snd $ unconsProjectConfigPath pa bImporters = snd $ unconsProjectConfigPath pb +splitPath :: FilePath -> [FilePath] +splitPath = FP.splitPath . normSep + where + normSep p = + if buildOS == Windows then + Windows.joinPath $ Windows.splitDirectories + [if Posix.isPathSeparator c then Windows.pathSeparator else c| c <- p] + else + Posix.joinPath $ Posix.splitDirectories + [if Windows.isPathSeparator c then Posix.pathSeparator else c| c <- p] + instance Binary ProjectConfigPath instance NFData ProjectConfigPath instance Structured ProjectConfigPath @@ -140,67 +186,10 @@ docProjectImportedBy (ProjectConfigPath (_ :| [])) = text "" docProjectImportedBy (ProjectConfigPath (_ :| ps)) = vcat $ [ text " " <+> text "imported by:" <+> quoteUntrimmed l | l <- ps ] - -- | If the path has leading or trailing spaces then show it quoted. quoteUntrimmed :: FilePath -> Doc quoteUntrimmed s = if trim s /= s then quotes (text s) else text s --- | Renders the paths as a list without showing which path imports another, --- like this; --- --- >- cabal.project --- >- project-cabal/constraints.config --- >- project-cabal/ghc-latest.config --- >- project-cabal/ghc-options.config --- >- project-cabal/pkgs.config --- >- project-cabal/pkgs/benchmarks.config --- >- project-cabal/pkgs/buildinfo.config --- >- project-cabal/pkgs/cabal.config --- >- project-cabal/pkgs/install.config --- >- project-cabal/pkgs/integration-tests.config --- >- project-cabal/pkgs/tests.config --- --- --- >>> :{ --- do --- let ps = --- [ ProjectConfigPath ("cabal.project" :| []) --- , ProjectConfigPath ("project-cabal/constraints.config" :| ["cabal.project"]) --- , ProjectConfigPath ("project-cabal/ghc-latest.config" :| ["cabal.project"]) --- , ProjectConfigPath ("project-cabal/ghc-options.config" :| ["cabal.project"]) --- , ProjectConfigPath ("project-cabal/pkgs.config" :| ["cabal.project"]) --- , ProjectConfigPath ("project-cabal/pkgs/benchmarks.config" :| ["project-cabal/pkgs.config","cabal.project"]) --- , ProjectConfigPath ("project-cabal/pkgs/buildinfo.config" :| ["project-cabal/pkgs.config","cabal.project"]) --- , ProjectConfigPath ("project-cabal/pkgs/cabal.config" :| ["project-cabal/pkgs.config","cabal.project"]) --- , ProjectConfigPath ("project-cabal/pkgs/install.config" :| ["project-cabal/pkgs.config","cabal.project"]) --- , ProjectConfigPath ("project-cabal/pkgs/integration-tests.config" :| ["project-cabal/pkgs.config","cabal.project"]) --- , ProjectConfigPath ("project-cabal/pkgs/tests.config" :| ["project-cabal/pkgs.config","cabal.project"]) --- ] --- return . render $ docProjectConfigFiles ps --- :} --- "- cabal.project\n- project-cabal/constraints.config\n- project-cabal/ghc-latest.config\n- project-cabal/ghc-options.config\n- project-cabal/pkgs.config\n- project-cabal/pkgs/benchmarks.config\n- project-cabal/pkgs/buildinfo.config\n- project-cabal/pkgs/cabal.config\n- project-cabal/pkgs/install.config\n- project-cabal/pkgs/integration-tests.config\n- project-cabal/pkgs/tests.config" -docProjectConfigFiles :: [ProjectConfigPath] -> Doc -docProjectConfigFiles ps = vcat - [ text "-" <+> text p - | p <- ordNub [ p | ProjectConfigPath (p :| _) <- ps ] - ] - --- | A message for a cyclical import, a "cyclical import of". -cyclicalImportMsg :: ProjectConfigPath -> Doc -cyclicalImportMsg path@(ProjectConfigPath (duplicate :| _)) = - vcat - [ text "cyclical import of" <+> text duplicate <> semi - , nest 2 (docProjectConfigPath path) - ] - --- | A message for an import that has leading or trailing spaces. -untrimmedUriImportMsg :: Doc -> ProjectConfigPath -> Doc -untrimmedUriImportMsg intro path = - vcat - [ intro <+> text "import has leading or trailing whitespace" <> semi - , nest 2 (docProjectConfigPath path) - ] - docProjectConfigPathFailReason :: VR -> ProjectConfigPath -> Doc docProjectConfigPathFailReason vr pcp | ProjectConfigPath (p :| []) <- pcp = diff --git a/cabal-install/cabal-install.cabal b/cabal-install/cabal-install.cabal index eeae37f45d5..207fbf92967 100644 --- a/cabal-install/cabal-install.cabal +++ b/cabal-install/cabal-install.cabal @@ -185,6 +185,7 @@ library Distribution.Client.ProjectBuilding.Types Distribution.Client.ProjectConfig Distribution.Client.ProjectConfig.FieldGrammar + Distribution.Client.ProjectConfig.Import Distribution.Client.ProjectConfig.Legacy Distribution.Client.ProjectConfig.Lens Distribution.Client.ProjectConfig.Parsec diff --git a/cabal-install/src/Distribution/Client/ProjectConfig.hs b/cabal-install/src/Distribution/Client/ProjectConfig.hs index c81b9c16535..4192d121a5b 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig.hs @@ -255,6 +255,7 @@ import System.IO , withBinaryFile ) +import Distribution.Client.ProjectConfig.Import (ProjectConfigSkeleton, docProjectConfigFiles, projectSkeletonImports) import Distribution.Deprecated.ProjectParseUtils (ProjectParseError (..), ProjectParseWarning) import Distribution.Solver.Types.ProjectConfigPath diff --git a/cabal-install/src/Distribution/Client/ProjectConfig/Import.hs b/cabal-install/src/Distribution/Client/ProjectConfig/Import.hs new file mode 100644 index 00000000000..c6fa0fbff09 --- /dev/null +++ b/cabal-install/src/Distribution/Client/ProjectConfig/Import.hs @@ -0,0 +1,147 @@ +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE NoImplicitPrelude #-} + +-- | Project configuration imports. +module Distribution.Client.ProjectConfig.Import + ( -- * Parsing skeleton + ProjectConfigSkeleton + , projectSkeletonImports + , fetchImport + + -- * Messages + , docProjectConfigFiles + , cyclicalImportMsg + , untrimmedUriImportMsg + ) where + +import Control.Arrow (Kleisli (..), arr, (>>>)) +import qualified Data.ByteString.Char8 as BS +import Data.Coerce (coerce) +import Distribution.Client.Compat.Prelude hiding (empty, (<>)) +import Distribution.Client.HttpUtils +import Distribution.Client.ProjectConfig.Types +import Distribution.Compat.Lens (view) +import Distribution.PackageDescription (ConfVar (..)) +import Distribution.Simple.Utils (debug, ordNub) +import Distribution.Solver.Types.ProjectConfigPath +import Distribution.Types.CondTree (CondTree (..), traverseCondTreeA) +import Distribution.Utils.String (trim) +import Network.URI (URI (..), parseURI) +import System.Directory (createDirectoryIfMissing) +import System.FilePath (isAbsolute, isPathSeparator, makeValid, ()) +import Text.PrettyPrint + +-- | 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 ([ProjectConfigPath], ProjectConfig) + +projectSkeletonImports :: ProjectConfigSkeleton -> [ProjectConfigPath] +projectSkeletonImports = fst . view traverseCondTreeA + +-- | Fetch a local file import or remote URL import and parse it. +fetchImport + :: (ProjectConfigToParse -> IO a) + -> FilePath + -> HttpTransport + -> Verbosity + -> FilePath + -> ProjectConfigPath + -> IO a +fetchImport parser cacheDir httpTransport verbosity projectDir normLocPath = + fetchImportConfig normLocPath >>= runKleisli (arr ProjectConfigToParse >>> Kleisli parser) . snd + where + fetchImportConfig :: ProjectConfigPath -> IO (Maybe URI, BS.ByteString) + fetchImportConfig (ProjectConfigPath (pci :| _)) = do + debug verbosity $ "fetching import: " ++ pci + let mbUri = parseURI (trim pci) + (mbUri,) <$> case mbUri 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 $ + if isAbsolute pci then pci else coerce projectDir pci + +-- | Renders the paths as a list without showing which path imports another, +-- like this; +-- +-- >- cabal.project +-- >- project-cabal/constraints.config +-- >- project-cabal/ghc-latest.config +-- >- project-cabal/ghc-options.config +-- >- project-cabal/pkgs.config +-- >- project-cabal/pkgs/benchmarks.config +-- >- project-cabal/pkgs/buildinfo.config +-- >- project-cabal/pkgs/cabal.config +-- >- project-cabal/pkgs/install.config +-- >- project-cabal/pkgs/integration-tests.config +-- >- project-cabal/pkgs/tests.config +-- +-- +-- >>> :{ +-- do +-- let ps = +-- [ ProjectConfigPath ("cabal.project" :| []) +-- , ProjectConfigPath ("project-cabal/constraints.config" :| ["cabal.project"]) +-- , ProjectConfigPath ("project-cabal/ghc-latest.config" :| ["cabal.project"]) +-- , ProjectConfigPath ("project-cabal/ghc-options.config" :| ["cabal.project"]) +-- , ProjectConfigPath ("project-cabal/pkgs.config" :| ["cabal.project"]) +-- , ProjectConfigPath ("project-cabal/pkgs/benchmarks.config" :| ["project-cabal/pkgs.config","cabal.project"]) +-- , ProjectConfigPath ("project-cabal/pkgs/buildinfo.config" :| ["project-cabal/pkgs.config","cabal.project"]) +-- , ProjectConfigPath ("project-cabal/pkgs/cabal.config" :| ["project-cabal/pkgs.config","cabal.project"]) +-- , ProjectConfigPath ("project-cabal/pkgs/install.config" :| ["project-cabal/pkgs.config","cabal.project"]) +-- , ProjectConfigPath ("project-cabal/pkgs/integration-tests.config" :| ["project-cabal/pkgs.config","cabal.project"]) +-- , ProjectConfigPath ("project-cabal/pkgs/tests.config" :| ["project-cabal/pkgs.config","cabal.project"]) +-- ] +-- return . render $ docProjectConfigFiles ps +-- :} +-- "- cabal.project\n- project-cabal/constraints.config\n- project-cabal/ghc-latest.config\n- project-cabal/ghc-options.config\n- project-cabal/pkgs.config\n- project-cabal/pkgs/benchmarks.config\n- project-cabal/pkgs/buildinfo.config\n- project-cabal/pkgs/cabal.config\n- project-cabal/pkgs/install.config\n- project-cabal/pkgs/integration-tests.config\n- project-cabal/pkgs/tests.config" +-- +-- The listing puts projects first, URLs last and sorts the other paths +-- lexically, dropping any duplicates, like this: +-- +-- >- cabal.project +-- >- 0.config +-- >- 2.config +-- >- cfg/1.config +-- >- cfg/3.config +-- >- with-ghc.config +-- >- https://www.stackage.org/lts-21.25/cabal.config +-- +-- >>> let p = ProjectConfigPath $ "cabal.project" :| [] +-- >>> let a = ProjectConfigPath $ "0.config" :| ["cabal.project"] +-- >>> let b = ProjectConfigPath $ "cfg/1.config" :| ["0.config", "cabal.project"] +-- >>> let c = ProjectConfigPath $ "with.config" :| ["0.config", "cabal.project"] +-- >>> let d = ProjectConfigPath $ "2.config" :| ["cfg/1.config", "0.config", "cabal.project"] +-- >>> let e = ProjectConfigPath $ "cfg/3.config" :| ["2.config", "cfg/1.config", "0.config", "cabal.project"] +-- >>> let f = ProjectConfigPath $ "https://www.stackage.org/lts-21.25/cabal.config" :| ["2.config", "cfg/1.config", "0.config", "cabal.project"] +-- >>> let g = ProjectConfigPath $ "https://www.stackage.org/lts-21.25/cabal.config" :| ["cfg/3.config", "2.config", "cfg/1.config", "0.config", "cabal.project"] +-- >>> let ps = [p, a, b, c, d, e, f, g] +-- >>> render $ docProjectConfigFiles ps +-- "- cabal.project\n- 0.config\n- 2.config\n- cfg/1.config\n- cfg/3.config\n- with.config\n- https://www.stackage.org/lts-21.25/cabal.config" +docProjectConfigFiles :: [ProjectConfigPath] -> Doc +docProjectConfigFiles (sortBy compareLexicographically -> ps) = + vcat + [ text "-" <+> text p + | p <- ordNub [p | ProjectConfigPath (p :| _) <- ps] + ] + +-- | A message for a cyclical import, a "cyclical import of". +cyclicalImportMsg :: ProjectConfigPath -> Doc +cyclicalImportMsg path@(ProjectConfigPath (duplicate :| _)) = + vcat + [ text "cyclical import of" <+> text duplicate <> semi + , nest 2 (docProjectConfigPath path) + ] + +-- | A message for an import that has leading or trailing spaces. +untrimmedUriImportMsg :: Doc -> ProjectConfigPath -> Doc +untrimmedUriImportMsg intro path = + vcat + [ intro <+> text "import has leading or trailing whitespace" <> semi + , nest 2 (docProjectConfigPath path) + ] diff --git a/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs b/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs index fc4faa6a64a..68d8520cc4c 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs @@ -11,12 +11,10 @@ -- | Project configuration, implementation in terms of legacy types. module Distribution.Client.ProjectConfig.Legacy ( -- Project config skeletons - ProjectConfigSkeleton - , parseProject + parseProject , instantiateProjectConfigSkeletonFetchingCompiler , instantiateProjectConfigSkeletonWithCompiler , singletonProjectConfigSkeleton - , projectSkeletonImports -- * Project config in terms of legacy types , LegacyProjectConfig @@ -34,7 +32,6 @@ module Distribution.Client.ProjectConfig.Legacy , renderPackageLocationToken ) where -import Data.Coerce (coerce) import Distribution.Client.Compat.Prelude import Distribution.Types.Flag (FlagName, parsecFlagAssignment) @@ -57,12 +54,13 @@ import Distribution.Client.CmdInstall.ClientInstallFlags , defaultClientInstallFlags ) -import Distribution.Compat.Lens (toListOf, view) +import Distribution.Compat.Lens (toListOf) import Distribution.Solver.Types.ConstraintSource import Distribution.Solver.Types.ProjectConfigPath import Distribution.Client.NixStyleOptions (NixStyleFlags (..)) +import Distribution.Client.ProjectConfig.Import (ProjectConfigSkeleton, cyclicalImportMsg, fetchImport, untrimmedUriImportMsg) import Distribution.Client.ProjectFlags (ProjectFlags (..), defaultProjectFlags, projectFlagsOptions) import Distribution.Client.Setup ( ConfigExFlags (..) @@ -140,7 +138,6 @@ import Distribution.Types.CondTree , ignoreConditions , mapTreeConds , mapTreeData - , traverseCondTreeA , traverseCondTreeV ) import Distribution.Types.SourceRepo (RepoType) @@ -149,7 +146,6 @@ import Distribution.Utils.NubList , overNubList , toNubList ) -import Distribution.Utils.String (trim) import Distribution.Client.HttpUtils import Distribution.Client.ParseUtils @@ -200,9 +196,9 @@ import qualified Data.ByteString.Char8 as BS import Data.Functor ((<&>)) import qualified Data.Map as Map import qualified Data.Set as Set -import Network.URI (URI (..), nullURIAuth, parseURI) -import System.Directory (createDirectoryIfMissing, makeAbsolute) -import System.FilePath (isAbsolute, isPathSeparator, makeValid, splitFileName, ()) +import Network.URI (URI (..), nullURIAuth) +import System.Directory (makeAbsolute) +import System.FilePath (splitFileName) import Text.PrettyPrint ( Doc , render @@ -214,10 +210,6 @@ import qualified Text.PrettyPrint as Disp -- 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 ([ProjectConfigPath], ProjectConfig) - singletonProjectConfigSkeleton :: ProjectConfig -> ProjectConfigSkeleton singletonProjectConfigSkeleton x = CondNode (mempty, x) mempty @@ -241,9 +233,6 @@ instantiateProjectConfigSkeletonWithCompiler os arch impl _flags skel = go $ map (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 -> [ProjectConfigPath] -projectSkeletonImports = fst . view traverseCondTreeA - -- | Parses a project from its root config file, typically cabal.project. parseProject :: FilePath @@ -293,9 +282,10 @@ parseProjectSkeleton cacheDir httpTransport verbosity projectDir source (Project when (isUntrimmedUriConfigPath importLocPath) (noticeDoc verbosity $ untrimmedUriImportMsg (Disp.text "Warning:") importLocPath) - let fs = (\z -> CondNode ([normLocPath], z) mempty) <$> fieldsToConfig normSource (reverse acc) - res <- parseProjectSkeleton cacheDir httpTransport verbosity projectDir importLocPath . ProjectConfigToParse =<< fetchImportConfig normLocPath + let parser = parseProjectSkeleton cacheDir httpTransport verbosity projectDir importLocPath + res <- fetchImport parser cacheDir httpTransport verbosity projectDir normLocPath rest <- go [] xs + let fs = (\z -> CondNode ([normLocPath], z) mempty) <$> fieldsToConfig normSource (reverse acc) pure . fmap mconcat . sequence $ [projectParse Nothing normSource fs, res, rest] (ParseUtils.Section l "if" p xs') -> do normSource <- canonicalizeConfigPath projectDir source @@ -361,22 +351,6 @@ parseProjectSkeleton cacheDir httpTransport verbosity projectDir source (Project addWarnings x' = x' liftPR p _ (ParseFailed e) = pure $ projectParseFail Nothing (Just p) e - fetchImportConfig :: ProjectConfigPath -> IO BS.ByteString - fetchImportConfig (ProjectConfigPath (pci :| _)) = do - debug verbosity $ "fetching import: " ++ pci - fetch pci - - fetch :: FilePath -> IO BS.ByteString - fetch pci = case parseURI $ trim 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 $ - if isAbsolute pci then pci else coerce projectDir pci - modifiesCompiler :: ProjectConfig -> Bool modifiesCompiler pc = isSet projectConfigHcFlavor || isSet projectConfigHcPath || isSet projectConfigHcPkg where diff --git a/cabal-install/src/Distribution/Client/ProjectConfig/Parsec.hs b/cabal-install/src/Distribution/Client/ProjectConfig/Parsec.hs index 90740fc7a93..866d635bff6 100644 --- a/cabal-install/src/Distribution/Client/ProjectConfig/Parsec.hs +++ b/cabal-install/src/Distribution/Client/ProjectConfig/Parsec.hs @@ -4,9 +4,7 @@ -- | Parsing project configuration. module Distribution.Client.ProjectConfig.Parsec ( -- * Package configuration - parseProjectSkeleton - , parseProject - , ProjectConfigSkeleton + parseProject , ProjectConfig (..) -- ** Parsing @@ -17,7 +15,7 @@ module Distribution.Client.ProjectConfig.Parsec import Distribution.CabalSpecVersion import Distribution.Client.HttpUtils import Distribution.Client.ProjectConfig.FieldGrammar (packageConfigFieldGrammar, projectConfigFieldGrammar) -import Distribution.Client.ProjectConfig.Legacy (ProjectConfigSkeleton) +import Distribution.Client.ProjectConfig.Import (ProjectConfigSkeleton, cyclicalImportMsg, fetchImport, untrimmedUriImportMsg) import qualified Distribution.Client.ProjectConfig.Lens as L import Distribution.Client.ProjectConfig.Types import Distribution.Client.Types.Repo hiding (repoName) @@ -48,19 +46,17 @@ import Distribution.Types.ConfVar (ConfVar (..)) import Distribution.Types.PackageName (PackageName) import Distribution.Utils.Generic (fromUTF8BS, toUTF8BS, validateUTF8) import Distribution.Utils.NubList (toNubList) -import Distribution.Utils.String (trim) import Distribution.Verbosity import Control.Monad.State.Strict (StateT, execStateT, lift) import qualified Data.ByteString as BS -import Data.Coerce (coerce) import qualified Data.Map.Strict as Map import qualified Data.Set as Set import Distribution.Client.Errors.Parser (ProjectFileSource (..)) import qualified Distribution.Compat.CharParsing as P -import Network.URI (parseURI, uriFragment, uriPath, uriScheme) -import System.Directory (createDirectoryIfMissing, makeAbsolute) -import System.FilePath (isAbsolute, isPathSeparator, makeValid, splitFileName, ()) +import Network.URI (uriFragment, uriPath, uriScheme) +import System.Directory (makeAbsolute) +import System.FilePath (splitFileName) import qualified Text.Parsec import Text.PrettyPrint (render) import qualified Text.PrettyPrint as Disp @@ -136,10 +132,10 @@ parseProjectSkeleton cacheDir httpTransport verbosity projectDir source (Project when (isUntrimmedUriConfigPath importLocPath) (noticeDoc verbosity $ untrimmedUriImportMsg (Disp.text "Warning:") importLocPath) - let fs = (\z -> CondNode ([normLocPath], z) mempty) <$> fieldsToConfig normSource (reverse acc) - importParseResult <- parseProjectSkeleton cacheDir httpTransport verbosity projectDir importLocPath . ProjectConfigToParse =<< fetchImportConfig normLocPath - + let parser = parseProjectSkeleton cacheDir httpTransport verbosity projectDir importLocPath + importParseResult <- fetchImport parser cacheDir httpTransport verbosity projectDir normLocPath rest <- go [] xs + let fs = (\z -> CondNode ([normLocPath], z) mempty) <$> fieldsToConfig normSource (reverse acc) pure . fmap mconcat . sequence $ [fs, importParseResult, rest] ) (parseImport pos importLines) @@ -191,22 +187,6 @@ parseProjectSkeleton cacheDir httpTransport verbosity projectDir source (Project config' <- view stateConfig <$> execStateT (goSections programDb sections) (SectionS config) return config' - fetchImportConfig :: ProjectConfigPath -> IO BS.ByteString - fetchImportConfig (ProjectConfigPath (pci :| _)) = do - debug verbosity $ "fetching import: " ++ pci - fetch pci - - fetch :: FilePath -> IO BS.ByteString - fetch pci = case parseURI (trim 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 $ - if isAbsolute pci then pci else coerce projectDir pci - modifiesCompiler :: ProjectConfig -> Bool modifiesCompiler pc = isSet projectConfigHcFlavor || isSet projectConfigHcPath || isSet projectConfigHcPkg where diff --git a/cabal-install/src/Distribution/Client/ProjectPlanning.hs b/cabal-install/src/Distribution/Client/ProjectPlanning.hs index 5be55634324..a27683886cd 100644 --- a/cabal-install/src/Distribution/Client/ProjectPlanning.hs +++ b/cabal-install/src/Distribution/Client/ProjectPlanning.hs @@ -121,6 +121,7 @@ import Distribution.Client.HttpUtils import Distribution.Client.JobControl import Distribution.Client.PackageHash import Distribution.Client.ProjectConfig +import Distribution.Client.ProjectConfig.Import (docProjectConfigFiles) import Distribution.Client.ProjectConfig.Legacy import Distribution.Client.ProjectConfig.Types (defaultProjectFileParser) import Distribution.Client.ProjectPlanOutput diff --git a/cabal-install/src/Distribution/Client/ScriptUtils.hs b/cabal-install/src/Distribution/Client/ScriptUtils.hs index c4f2925651e..6127e544e1b 100644 --- a/cabal-install/src/Distribution/Client/ScriptUtils.hs +++ b/cabal-install/src/Distribution/Client/ScriptUtils.hs @@ -58,9 +58,9 @@ import Distribution.Client.ProjectConfig , withGlobalConfig , withProjectOrGlobalConfig ) +import Distribution.Client.ProjectConfig.Import (ProjectConfigSkeleton) import Distribution.Client.ProjectConfig.Legacy - ( ProjectConfigSkeleton - , instantiateProjectConfigSkeletonFetchingCompiler + ( instantiateProjectConfigSkeletonFetchingCompiler , parseProject ) import Distribution.Client.ProjectConfig.Types (ProjectConfigToParse (..)) diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/hops.expect.txt b/cabal-testsuite/PackageTests/ConditionalAndImport/hops.expect.txt index bf3ea9bc001..aa2a17291d1 100644 --- a/cabal-testsuite/PackageTests/ConditionalAndImport/hops.expect.txt +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/hops.expect.txt @@ -1,42 +1,42 @@ Configuration is affected by the following files: - hops-0.project +- hops/hops-1.config + imported by: hops-0.project - hops-2.config imported by: hops/hops-1.config imported by: hops-0.project +- hops/hops-3.config + imported by: hops-2.config + imported by: hops/hops-1.config + imported by: hops-0.project - hops-4.config imported by: hops/hops-3.config imported by: hops-2.config imported by: hops/hops-1.config imported by: hops-0.project -- hops-6.config - imported by: hops/hops-5.config +- hops/hops-5.config imported by: hops-4.config imported by: hops/hops-3.config imported by: hops-2.config imported by: hops/hops-1.config imported by: hops-0.project -- hops-8.config - imported by: hops/hops-7.config - imported by: hops-6.config +- hops-6.config imported by: hops/hops-5.config imported by: hops-4.config imported by: hops/hops-3.config imported by: hops-2.config imported by: hops/hops-1.config imported by: hops-0.project -- hops/hops-1.config - imported by: hops-0.project -- hops/hops-3.config - imported by: hops-2.config - imported by: hops/hops-1.config - imported by: hops-0.project -- hops/hops-5.config +- hops/hops-7.config + imported by: hops-6.config + imported by: hops/hops-5.config imported by: hops-4.config imported by: hops/hops-3.config imported by: hops-2.config imported by: hops/hops-1.config imported by: hops-0.project -- hops/hops-7.config +- hops-8.config + imported by: hops/hops-7.config imported by: hops-6.config imported by: hops/hops-5.config imported by: hops-4.config diff --git a/cabal.validate.project b/cabal.validate.project index bf2552af2d0..d1d57e0feba 100644 --- a/cabal.validate.project +++ b/cabal.validate.project @@ -13,7 +13,7 @@ program-options -- The +legacy-comparison flag is to set the default project file parser to compare -- the result of the legacy and parsec parser and fail if they are not equal. package cabal-install - flags: +git-rev +legacy-comparison + flags: +git-rev package Cabal flags: +git-rev diff --git a/changelog.d/pr11773 b/changelog.d/pr11773 new file mode 100644 index 00000000000..f8d633b535d --- /dev/null +++ b/changelog.d/pr11773 @@ -0,0 +1,10 @@ +packages: cabal-install-solver cabal-install +prs: #11773 +issues: #9562 #10512 +synopsis: Move project configuration import path functions to `cabal-install` +description: + Move these functions from `cabal-install-solver` to `cabal-install`: + + - `docProjectConfigFiles` + - `cyclicalImportMsg` + - `untrimmedUriImportMsg`