Skip to content

Commit 731f699

Browse files
authored
Merge pull request haskell#10629 from cabalism/fix/project-untrimmed-url
Have projects import trimmed URIs
2 parents 3bbc15a + f6c3a43 commit 731f699

File tree

11 files changed

+129
-9
lines changed

11 files changed

+129
-9
lines changed

Cabal/src/Distribution/Simple/Configure.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ import Distribution.Types.MissingDependencyReason (MissingDependencyReason (..))
114114
import Distribution.Types.PackageVersionConstraint
115115
import Distribution.Utils.LogProgress
116116
import Distribution.Utils.NubList
117+
import Distribution.Utils.String (trim)
117118
import Distribution.Verbosity
118119
import Distribution.Version
119120

@@ -2397,7 +2398,6 @@ configurePkgconfigPackages verbosity pkg_descr progdb enabled
23972398
pkgconfig ["--modversion", pkg]
23982399
`catchIO` (\_ -> dieWithException verbosity $ PkgConfigNotFound pkg versionRequirement)
23992400
`catchExit` (\_ -> dieWithException verbosity $ PkgConfigNotFound pkg versionRequirement)
2400-
let trim = dropWhile isSpace . dropWhileEnd isSpace
24012401
let v = PkgconfigVersion (toUTF8BS $ trim version)
24022402
if not (withinPkgconfigVersionRange v range)
24032403
then dieWithException verbosity $ BadVersion pkg versionRequirement v

cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs

+41-7
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ module Distribution.Solver.Types.ProjectConfigPath
1414
, docProjectConfigPath
1515
, docProjectConfigFiles
1616
, cyclicalImportMsg
17+
, untrimmedUriImportMsg
1718
, docProjectConfigPathFailReason
1819

1920
-- * Checks and Normalization
2021
, isCyclicConfigPath
2122
, isTopLevelConfigPath
23+
, isUntrimmedUriConfigPath
2224
, canonicalizeConfigPath
2325
) where
2426

@@ -34,6 +36,7 @@ import System.FilePath
3436
import qualified Data.List.NonEmpty as NE
3537
import Distribution.Solver.Modular.Version (VR)
3638
import Distribution.Pretty (prettyShow)
39+
import Distribution.Utils.String (trim)
3740
import Text.PrettyPrint
3841
import Distribution.Simple.Utils (ordNub)
3942

@@ -98,9 +101,13 @@ instance Structured ProjectConfigPath
98101
-- >>> render . docProjectConfigPath $ ProjectConfigPath $ "D.config" :| ["C.config", "B.config", "A.project"]
99102
-- "D.config\n imported by: C.config\n imported by: B.config\n imported by: A.project"
100103
docProjectConfigPath :: ProjectConfigPath -> Doc
101-
docProjectConfigPath (ProjectConfigPath (p :| [])) = text p
102-
docProjectConfigPath (ProjectConfigPath (p :| ps)) = vcat $
103-
text p : [ text " " <+> text "imported by:" <+> text l | l <- ps ]
104+
docProjectConfigPath (ProjectConfigPath (p :| [])) = quoteUntrimmed p
105+
docProjectConfigPath (ProjectConfigPath (p :| ps)) = vcat $ quoteUntrimmed p :
106+
[ text " " <+> text "imported by:" <+> quoteUntrimmed l | l <- ps ]
107+
108+
-- | If the path has leading or trailing spaces then show it quoted.
109+
quoteUntrimmed :: FilePath -> Doc
110+
quoteUntrimmed s = if trim s /= s then quotes (text s) else text s
104111

105112
-- | Renders the paths as a list without showing which path imports another,
106113
-- like this;
@@ -150,6 +157,14 @@ cyclicalImportMsg path@(ProjectConfigPath (duplicate :| _)) =
150157
, nest 2 (docProjectConfigPath path)
151158
]
152159

160+
-- | A message for an import that has leading or trailing spaces.
161+
untrimmedUriImportMsg :: Doc -> ProjectConfigPath -> Doc
162+
untrimmedUriImportMsg intro path =
163+
vcat
164+
[ intro <+> text "import has leading or trailing whitespace" <> semi
165+
, nest 2 (docProjectConfigPath path)
166+
]
167+
153168
docProjectConfigPathFailReason :: VR -> ProjectConfigPath -> Doc
154169
docProjectConfigPathFailReason vr pcp
155170
| ProjectConfigPath (p :| []) <- pcp =
@@ -178,6 +193,11 @@ nullProjectConfigPath = ProjectConfigPath $ "unused" :| []
178193
isCyclicConfigPath :: ProjectConfigPath -> Bool
179194
isCyclicConfigPath (ProjectConfigPath p) = length p /= length (NE.nub p)
180195

196+
-- | Check if the last segment of the path (root or importee) is a URI that has
197+
-- leading or trailing spaces.
198+
isUntrimmedUriConfigPath :: ProjectConfigPath -> Bool
199+
isUntrimmedUriConfigPath (ProjectConfigPath (p :| _)) = let p' = trim p in p' /= p && isURI p'
200+
181201
-- | Check if the project config path is top-level, meaning it was not included by
182202
-- some other project config.
183203
isTopLevelConfigPath :: ProjectConfigPath -> Bool
@@ -196,7 +216,7 @@ unconsProjectConfigPath ps = fmap ProjectConfigPath <$> NE.uncons (coerce ps)
196216
makeRelativeConfigPath :: FilePath -> ProjectConfigPath -> ProjectConfigPath
197217
makeRelativeConfigPath dir (ProjectConfigPath p) =
198218
ProjectConfigPath
199-
$ (\segment -> (if isURI segment then segment else makeRelative dir segment))
219+
$ (\segment@(trim -> trimSegment) -> (if isURI trimSegment then trimSegment else makeRelative dir segment))
200220
<$> p
201221

202222
-- | Normalizes and canonicalizes a path removing '.' and '..' indirections.
@@ -273,11 +293,25 @@ makeRelativeConfigPath dir (ProjectConfigPath p) =
273293
-- return $ expected == render (docProjectConfigPath p) ++ "\n"
274294
-- :}
275295
-- True
296+
--
297+
-- "A string is a valid URL potentially surrounded by spaces if, after stripping leading and trailing whitespace from it, it is a valid URL."
298+
-- [W3C/HTML5/URLs](https://www.w3.org/TR/2010/WD-html5-20100624/urls.html)
299+
--
300+
-- Trailing spaces for @ProjectConfigPath@ URLs are trimmed.
301+
--
302+
-- >>> p <- canonicalizeConfigPath "" (ProjectConfigPath $ ("https://www.stackage.org/nightly-2024-12-05/cabal.config ") :| [])
303+
-- >>> render $ docProjectConfigPath p
304+
-- "https://www.stackage.org/nightly-2024-12-05/cabal.config"
305+
--
306+
-- >>> let d = testDir
307+
-- >>> p <- canonicalizeConfigPath d (ProjectConfigPath $ ("https://www.stackage.org/nightly-2024-12-05/cabal.config ") :| [d </> "cabal.project"])
308+
-- >>> render $ docProjectConfigPath p
309+
-- "https://www.stackage.org/nightly-2024-12-05/cabal.config\n imported by: cabal.project"
276310
canonicalizeConfigPath :: FilePath -> ProjectConfigPath -> IO ProjectConfigPath
277311
canonicalizeConfigPath d (ProjectConfigPath p) = do
278-
xs <- sequence $ NE.scanr (\importee -> (>>= \importer ->
279-
if isURI importee
280-
then pure importee
312+
xs <- sequence $ NE.scanr (\importee@(trim -> trimImportee) -> (>>= \importer@(trim -> trimImporter) ->
313+
if isURI trimImportee || isURI trimImporter
314+
then pure trimImportee
281315
else canonicalizePath $ d </> takeDirectory importer </> importee))
282316
(pure ".") p
283317
return . makeRelativeConfigPath d . ProjectConfigPath . NE.fromList $ NE.init xs

cabal-install/src/Distribution/Client/ProjectConfig/Legacy.hs

+6-1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ import Distribution.Simple.Setup
126126
import Distribution.Simple.Utils
127127
( debug
128128
, lowercase
129+
, noticeDoc
129130
)
130131
import Distribution.Types.CondTree
131132
( CondBranch (..)
@@ -141,6 +142,7 @@ import Distribution.Utils.NubList
141142
, overNubList
142143
, toNubList
143144
)
145+
import Distribution.Utils.String (trim)
144146

145147
import Distribution.Client.HttpUtils
146148
import Distribution.Client.ParseUtils
@@ -274,6 +276,9 @@ parseProjectSkeleton cacheDir httpTransport verbosity projectDir source (Project
274276
if isCyclicConfigPath normLocPath
275277
then pure . parseFail $ ParseUtils.FromString (render $ cyclicalImportMsg normLocPath) Nothing
276278
else do
279+
when
280+
(isUntrimmedUriConfigPath importLocPath)
281+
(noticeDoc verbosity $ untrimmedUriImportMsg (Disp.text "Warning:") importLocPath)
277282
normSource <- canonicalizeConfigPath projectDir source
278283
let fs = (\z -> CondNode z [normLocPath] mempty) <$> fieldsToConfig normSource (reverse acc)
279284
res <- parseProjectSkeleton cacheDir httpTransport verbosity projectDir importLocPath . ProjectConfigToParse =<< fetchImportConfig normLocPath
@@ -342,7 +347,7 @@ parseProjectSkeleton cacheDir httpTransport verbosity projectDir source (Project
342347
fetch pci
343348

344349
fetch :: FilePath -> IO BS.ByteString
345-
fetch pci = case parseURI pci of
350+
fetch pci = case parseURI $ trim pci of
346351
Just uri -> do
347352
let fp = cacheDir </> map (\x -> if isPathSeparator x then '_' else x) (makeValid $ show uri)
348353
createDirectoryIfMissing True cacheDir
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# checking project import with trailing space
2+
# cabal v2-build
3+
Warning: import has leading or trailing whitespace;
4+
'https://www.stackage.org/nightly-2024-12-05/cabal.config '
5+
imported by: trailing-space.project
6+
Configuration is affected by the following files:
7+
- trailing-space.project
8+
- with-ghc.config
9+
imported by: trailing-space.project
10+
- https://www.stackage.org/nightly-2024-12-05/cabal.config
11+
imported by: trailing-space.project
12+
Resolving dependencies...
13+
Build profile: -w ghc-<GHCVER> -O1
14+
In order, the following would be built:
15+
- my-0.1 (lib:my) (first run)
16+
# checking project import with tabs and spaces
17+
# cabal v2-build
18+
Warning: import has leading or trailing whitespace;
19+
'https://www.stackage.org/nightly-2024-12-05/cabal.config '
20+
imported by: tabs-and-spaces.project
21+
Configuration is affected by the following files:
22+
- tabs-and-spaces.project
23+
- with-ghc.config
24+
imported by: tabs-and-spaces.project
25+
- https://www.stackage.org/nightly-2024-12-05/cabal.config
26+
imported by: tabs-and-spaces.project
27+
Resolving dependencies...
28+
Build profile: -w ghc-<GHCVER> -O1
29+
In order, the following would be built:
30+
- my-0.1 (lib:my) (first run)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Test.Cabal.Prelude
2+
3+
main = cabalTest . recordMode RecordMarked $ do
4+
let log = recordHeader . pure
5+
6+
log "checking project import with trailing space"
7+
trailing <- cabal' "v2-build" [ "--dry-run", "--project-file=trailing-space.project" ]
8+
assertOutputContains "import has leading or trailing whitespace" trailing
9+
assertOutputContains "'https://www.stackage.org/nightly-2024-12-05/cabal.config '" trailing
10+
11+
log "checking project import with tabs and spaces"
12+
cabal "v2-build" [ "--dry-run", "--project-file=tabs-and-spaces.project" ]
13+
14+
return ()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: my
2+
version: 0.1
3+
license: BSD3
4+
cabal-version: >= 1.2
5+
build-type: Simple
6+
7+
library
8+
exposed-modules: Foo
9+
build-depends: base
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
packages: .
2+
import: https://www.stackage.org/nightly-2024-12-05/cabal.config
3+
import: with-ghc.config
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
packages: .
2+
import: https://www.stackage.org/nightly-2024-12-05/cabal.config
3+
import: with-ghc.config
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- WARNING: Override the `with-compiler: ghc-x.y.z` of the stackage import, of
2+
-- https://www.stackage.org/nightly-yyyy-mm-dd/cabal.config. Otherwise tests
3+
-- will fail with:
4+
-- -Error: [Cabal-5490]
5+
-- -Cannot find the program 'ghc'. User-specified path 'ghc-x.y.z' does not
6+
-- refer to an executable and the program is not on the system path.
7+
with-compiler: ghc

changelog.d/pr-10629

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
synopsis: "Report trailing spaces in project import URIs"
3+
packages: [cabal-install, cabal-install-solver]
4+
prs: 10629
5+
issues: 10622
6+
---
7+
8+
> A string is a valid URL potentially surrounded by spaces if, after stripping
9+
> leading and trailing whitespace from it, it is a valid URL."
10+
> SOURCE: [W3C/HTML5/URLs](https://www.w3.org/TR/2010/WD-html5-20100624/urls.html)
11+
12+
Fixes a problem of mistaking a URI for a file path when it has trailing spaces
13+
and warn about such trailing spaces.

fix-whitespace.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ excluded-files:
9595
- Cabal-tests/tests/ParserTests/warnings/tab.cabal
9696
- Cabal-tests/tests/ParserTests/warnings/utf8.cabal
9797
- cabal-testsuite/PackageTests/Regression/T8507/pkg.cabal
98+
- cabal-testsuite/PackageTests/ProjectImport/UntrimmedImport/trailing-space.project
99+
- cabal-testsuite/PackageTests/ProjectImport/UntrimmedImport/tabs-and-spaces.project
98100

99101
# These also contain tabs that affect the golden value:
100102
# Could be removed from exceptions, but then the tab warning

0 commit comments

Comments
 (0)