-
Notifications
You must be signed in to change notification settings - Fork 733
Expand file tree
/
Copy pathPath.hs
More file actions
553 lines (454 loc) · 19.1 KB
/
Path.hs
File metadata and controls
553 lines (454 loc) · 19.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Distribution.Utils.Path
( -- * Symbolic path endpoints
FileOrDir (..)
, AllowAbsolute (..)
-- ** Abstract directory locations
, CWD
, Pkg
, Dist
, Source
, Include
, Lib
, Framework
, Build
, Artifacts
, PkgDB
, DataDir
, Mix
, Tix
, Tmp
, Response
, PkgConf
-- * Symbolic paths
, RelativePath
, SymbolicPath
, AbsolutePath (..)
, SymbolicPathX -- NB: constructor not exposed, to retain type safety.
, FileLike (..)
, PathLike (..)
-- ** Symbolic path API
, getSymbolicPath
, getAbsolutePath
, sameDirectory
, makeRelativePathEx
, makeSymbolicPath
, unsafeMakeSymbolicPath
, coerceSymbolicPath
, unsafeCoerceSymbolicPath
, relativeSymbolicPath
, symbolicPathRelative_maybe
, interpretSymbolicPath
, interpretSymbolicPathAbsolute
-- ** General filepath API
, takeDirectorySymbolicPath
, dropExtensionsSymbolicPath
, replaceExtensionSymbolicPath
, normaliseSymbolicPath
, relativePathMaybe
-- ** Working directory handling
, interpretSymbolicPathCWD
, absoluteWorkingDir
, tryMakeRelative
-- ** Module names
, moduleNameSymbolicPath
) where
import Distribution.Compat.Prelude
import Prelude ()
import Data.Coerce
import Distribution.ModuleName (ModuleName)
import qualified Distribution.ModuleName as ModuleName
( toFilePath
)
import Distribution.Parsec
import Distribution.Pretty
import Distribution.Utils.Generic (isAbsoluteOnAnyPlatform)
import qualified Distribution.Compat.CharParsing as P
import qualified System.Directory as Directory
import qualified System.FilePath as FilePath
import Data.Kind
( Type
)
import Data.List
( stripPrefix
)
import GHC.Stack
( HasCallStack
)
-------------------------------------------------------------------------------
-- * SymbolicPath
-------------------------------------------------------------------------------
{- Note [Symbolic paths]
~~~~~~~~~~~~~~~~~~~~~~~~
We want functions within the Cabal library to support getting the working
directory from their arguments, rather than retrieving it from the current
directory, which depends on the the state of the current process
(via getCurrentDirectory).
With such a constraint, to ensure correctness we need to know, for each relative
filepath, whether it is relative to the passed in working directory or to the
current working directory. We achieve this with the following API:
- newtype SymbolicPath from to
- getSymbolicPath :: SymbolicPath from to -> FilePath
- interpretSymbolicPath
:: Maybe (SymbolicPath CWD (Dir from)) -> SymbolicPath from to -> FilePath
Note that, in the type @SymbolicPath from to@, @from@ is the name of a directory,
whereas @to@ is either @Dir toDir@ or @File@. For example, a source directory
typically has the type @SymbolicPath Pkg (Dir Source)@, while a source
file has a type such as @SymbolicPath "Source" File@.
Here, a symbolic path refers to an **uninterpreted** file path, i.e. any
passed in working directory **has not** been taken into account.
Whenever we see a symbolic path, it is a sign we must take into account this
working directory in some way.
Thus, whenever we interact with the file system, we do the following:
- in a direct interaction (e.g. `doesFileExist`), we interpret the
path relative to a working directory argument, e.g.
doCheck :: Maybe (SymbolicPath CWD (Dir from))
-> SymbolicPath from File
-> Bool
doCheck mbWorkDir file = doesFileExist $ interpretSymbolicPath mbWorkDir file
- when invoking a sub-process (such as GHC), we ensure that the working directory
of the sub-process is the same as the passed-in working directory, in which
case we interpret the symbolic paths by using `interpretSymbolicPathCWD`:
callGhc :: Maybe (SymbolicPath CWD (Dir Pkg))
-> SymbolicPath (Dir Pkg) File
-> IO ()
callGhc mbWorkDir inputFile =
runProgramInvocation $
programInvocationCwd mbWorkDir ghcProg [interpretSymbolicPathCWD inputFile]
In practice, we often use:
-- Interpret a symbolic path with respect to the working directory argument
-- @'mbWorkDir' :: Maybe (SymbolicPath CWD (Dir Pkg))@.
i :: SymbolicPath Pkg to -> FilePath
i = interpretSymbolicPath mbWorkDir
-- Interpret a symbolic path, provided that the current working directory
-- is the package directory.
u :: SymbolicPath Pkg to -> FilePath
u = interpretSymbolicPathCWD
Note [Symbolic relative paths]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module defines:
data kind AllowAbsolute = AllowAbsolute | OnlyRelative
data kind FileOrDir = File | Dir Symbol
type SymbolicPathX :: AllowAbsolute -> Symbol -> FileOrDir -> Type
newtype SymbolicPathX allowAbsolute from to = SymbolicPath FilePath
type RelativePath = SymbolicPathX 'OnlyRelative
type SymbolicPath = SymbolicPathX 'AllowAbsolute
A 'SymbolicPath' is thus a symbolic path that is allowed to be absolute, whereas
a 'RelativePath' is a symbolic path that is additionally required to be relative.
This distinction allows us to keep track of which filepaths must be kept
relative.
-}
-- | A type-level symbolic name, to an abstract file or directory
-- (e.g. the Cabal package directory).
data FileOrDir
= -- | A file (with no further information).
File
| -- | The abstract name of a directory or category of directories,
-- e.g. the package directory or a source directory.
Dir Type
-- | Is this symbolic path allowed to be absolute, or must it be relative?
data AllowAbsolute
= -- | The path may be absolute, or it may be relative.
AllowAbsolute
| -- | The path must be relative.
OnlyRelative
-- | A symbolic path, possibly relative to an abstract location specified
-- by the @from@ type parameter.
--
-- They are *symbolic*, which means we cannot perform any 'IO'
-- until we interpret them (using e.g. 'interpretSymbolicPath').
newtype SymbolicPathX (allowAbsolute :: AllowAbsolute) (from :: Type) (to :: FileOrDir)
= SymbolicPath FilePath
deriving (Generic, Show, Read, Eq, Ord, Data)
type role SymbolicPathX nominal nominal nominal
-- | A symbolic relative path, relative to an abstract location specified
-- by the @from@ type parameter.
--
-- They are *symbolic*, which means we cannot perform any 'IO'
-- until we interpret them (using e.g. 'interpretSymbolicPath').
type RelativePath = SymbolicPathX 'OnlyRelative
-- | A symbolic path which is allowed to be absolute.
--
-- They are *symbolic*, which means we cannot perform any 'IO'
-- until we interpret them (using e.g. 'interpretSymbolicPath').
type SymbolicPath = SymbolicPathX 'AllowAbsolute
newtype AbsolutePath (to :: FileOrDir) = AbsolutePath (forall from. SymbolicPath from to)
unsafeMakeAbsolutePath :: FilePath -> AbsolutePath to
unsafeMakeAbsolutePath fp = AbsolutePath (makeSymbolicPath fp)
instance Binary (SymbolicPathX allowAbsolute from to)
instance
(Typeable allowAbsolute, Typeable from, Typeable to)
=> Structured (SymbolicPathX allowAbsolute from to)
instance NFData (SymbolicPathX allowAbsolute from to) where rnf = genericRnf
-- | Extract the 'FilePath' underlying a 'SymbolicPath' or 'RelativePath',
-- without interpreting it.
--
-- Use this function e.g. to validate the underlying filepath.
--
-- When interacting with the file system, you should instead use
-- 'interpretSymbolicPath' or 'interpretSymbolicPathCWD'.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
getSymbolicPath :: SymbolicPathX allowAbsolute from to -> FilePath
getSymbolicPath (SymbolicPath p) = p
-- | A symbolic path from a directory to itself.
sameDirectory :: SymbolicPathX allowAbsolute from (Dir to)
sameDirectory = SymbolicPath "."
-- | Make a 'RelativePath', ensuring the path is not absolute,
-- but performing no further checks.
makeRelativePathEx :: HasCallStack => FilePath -> RelativePath from to
makeRelativePathEx fp
| isAbsoluteOnAnyPlatform fp =
error $ "Cabal internal error: makeRelativePathEx: absolute path " ++ fp
| otherwise =
SymbolicPath fp
-- | Make a 'SymbolicPath', which may be relative or absolute.
makeSymbolicPath :: FilePath -> SymbolicPath from to
makeSymbolicPath fp = SymbolicPath fp
-- | Make a 'SymbolicPath' which may be relative or absolute,
-- without performing any checks.
--
-- Avoid using this function in new code.
unsafeMakeSymbolicPath :: FilePath -> SymbolicPathX allowAbs from to
unsafeMakeSymbolicPath fp = SymbolicPath fp
-- | Like 'System.FilePath.takeDirectory', for symbolic paths.
takeDirectorySymbolicPath :: SymbolicPathX allowAbsolute from File -> SymbolicPathX allowAbsolute from (Dir to')
takeDirectorySymbolicPath (SymbolicPath fp) = SymbolicPath (FilePath.takeDirectory fp)
-- | Like 'System.FilePath.dropExtensions', for symbolic paths.
dropExtensionsSymbolicPath :: SymbolicPathX allowAbsolute from File -> SymbolicPathX allowAbsolute from File
dropExtensionsSymbolicPath (SymbolicPath fp) = SymbolicPath (FilePath.dropExtensions fp)
-- | Like 'System.FilePath.replaceExtension', for symbolic paths.
replaceExtensionSymbolicPath :: SymbolicPathX allowAbsolute from File -> String -> SymbolicPathX allowAbsolute from File
replaceExtensionSymbolicPath (SymbolicPath fp) ext = SymbolicPath (FilePath.replaceExtension fp ext)
-- | Like 'System.FilePath.normalise', for symbolic paths.
normaliseSymbolicPath :: SymbolicPathX allowAbsolute from to -> SymbolicPathX allowAbsolute from to
normaliseSymbolicPath (SymbolicPath fp) = SymbolicPath (FilePath.normalise fp)
-- | Retrieve the relative symbolic path to a Haskell module.
moduleNameSymbolicPath :: ModuleName -> SymbolicPathX allowAbsolute Source File
moduleNameSymbolicPath modNm = SymbolicPath $ ModuleName.toFilePath modNm
-- | Interpret a symbolic path with respect to the given directory.
--
-- Use this function before directly interacting with the file system in order
-- to take into account a working directory argument.
--
-- NB: when invoking external programs (such as @GHC@), it is preferable to set
-- the working directory of the process and use 'interpretSymbolicPathCWD'
-- rather than calling this function, as this function will turn relative paths
-- into absolute paths if the working directory is an absolute path.
-- This can degrade error messages, or worse, break the behaviour entirely
-- (because the program might expect certain paths to be relative).
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
interpretSymbolicPath :: Maybe (SymbolicPath CWD (Dir from)) -> SymbolicPathX allowAbsolute from to -> FilePath
interpretSymbolicPath mbWorkDir (SymbolicPath p) =
-- Note that this properly handles an absolute symbolic path,
-- because if @q@ is absolute, then @p </> q = q@.
maybe p ((</> p) . getSymbolicPath) mbWorkDir
-- | Interpret a symbolic path, **under the assumption that the working
-- directory is the package directory**.
--
-- Use 'interpretSymbolicPath' instead if you need to take into account a
-- working directory argument before directly interacting with the file system.
--
-- Use this function instead of 'interpretSymbolicPath' when invoking a child
-- process: set the working directory of the sub-process, and use this function,
-- e.g.:
--
-- > callGhc :: Maybe (SymbolicPath CWD (Dir Pkg))
-- > -> SymbolicPath (Dir Pkg) File
-- > -> IO ()
-- > callGhc mbWorkDir inputFile =
-- > runProgramInvocation $
-- > programInvocationCwd mbWorkDir ghcProg [interpretSymbolicPathCWD inputFile]
--
-- In this example, 'programInvocationCwd' sets the working directory, so it is
-- appropriate to use 'interpretSymbolicPathCWD' to provide its arguments.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
interpretSymbolicPathCWD :: SymbolicPathX allowAbsolute from to -> FilePath
interpretSymbolicPathCWD (SymbolicPath p) = p
getAbsolutePath :: AbsolutePath to -> FilePath
getAbsolutePath (AbsolutePath p) = getSymbolicPath p
interpretSymbolicPathAbsolute :: AbsolutePath (Dir Pkg) -> SymbolicPathX allowAbsolute Pkg to -> FilePath
interpretSymbolicPathAbsolute (AbsolutePath p) sym = interpretSymbolicPath (Just p) sym
-- | Change what a symbolic path is pointing to.
coerceSymbolicPath :: SymbolicPathX allowAbsolute from to1 -> SymbolicPathX allowAbsolute from to2
coerceSymbolicPath = coerce
-- | Does the second argument point to a sub-directory of the first one?
-- If so, return the relative portion of the path, relative to the first argument.
relativePathMaybe :: SymbolicPath from (Dir dir) -> SymbolicPath from to -> Maybe (RelativePath dir to)
relativePathMaybe base fp =
let dirPieces =
FilePath.splitDirectories $
FilePath.dropTrailingPathSeparator $
FilePath.normalise $
getSymbolicPath base
pathPieces =
FilePath.splitDirectories $
FilePath.normalise $
getSymbolicPath fp
in unsafeMakeSymbolicPath . FilePath.joinPath
<$> stripPrefix dirPieces pathPieces
-- | Change both what a symbolic path is pointing from and pointing to.
--
-- Avoid using this in new code.
unsafeCoerceSymbolicPath :: SymbolicPathX allowAbsolute from1 to1 -> SymbolicPathX allowAbsolute from2 to2
unsafeCoerceSymbolicPath = coerce
-- | Weakening: convert a relative symbolic path to a symbolic path,
-- \"forgetting\" that it is relative.
relativeSymbolicPath :: RelativePath from to -> SymbolicPath from to
relativeSymbolicPath (SymbolicPath fp) = SymbolicPath fp
-- | Is this symbolic path a relative symbolic path?
symbolicPathRelative_maybe :: SymbolicPath from to -> Maybe (RelativePath from to)
symbolicPathRelative_maybe (SymbolicPath fp) =
if isAbsoluteOnAnyPlatform fp
then Nothing
else Just $ SymbolicPath fp
-- | Absolute path to the current working directory.
absoluteWorkingDir :: Maybe (SymbolicPath CWD to) -> IO (AbsolutePath to)
absoluteWorkingDir Nothing = unsafeMakeAbsolutePath <$> Directory.getCurrentDirectory
absoluteWorkingDir (Just wd) = unsafeMakeAbsolutePath <$> Directory.makeAbsolute (getSymbolicPath wd)
-- | Try to make a symbolic path relative.
--
-- This function does nothing if the path is already relative.
--
-- NB: this function may fail to make the path relative.
tryMakeRelative :: Maybe (SymbolicPath CWD (Dir dir)) -> SymbolicPath dir to -> IO (SymbolicPath dir to)
tryMakeRelative mbWorkDir (SymbolicPath fp) = do
AbsolutePath wd <- absoluteWorkingDir mbWorkDir
return $ SymbolicPath (FilePath.makeRelative (getSymbolicPath wd) fp)
-------------------------------------------------------------------------------
-- ** Parsing and pretty printing
-------------------------------------------------------------------------------
instance Parsec (SymbolicPathX 'OnlyRelative from to) where
parsec = do
token <- parsecToken
if null token
then P.unexpected "empty FilePath"
else
if isAbsoluteOnAnyPlatform token
then P.unexpected "absolute FilePath"
else return (SymbolicPath token)
instance Parsec (SymbolicPathX 'AllowAbsolute from to) where
parsec = do
token <- parsecToken
if null token
then P.unexpected "empty FilePath"
else return (SymbolicPath token)
instance Pretty (SymbolicPathX allowAbsolute from to) where
pretty = showFilePath . getSymbolicPath
-------------------------------------------------------------------------------
-- * Composition
-------------------------------------------------------------------------------
infixr 7 <.>
-- | Types that support 'System.FilePath.<.>'.
class FileLike p where
-- | Like 'System.FilePath.<.>', but also supporting symbolic paths.
(<.>) :: p -> String -> p
instance FileLike FilePath where
(<.>) = (FilePath.<.>)
instance p ~ File => FileLike (SymbolicPathX allowAbsolute dir p) where
SymbolicPath p <.> ext = SymbolicPath (p <.> ext)
infixr 5 </>
-- | Types that support 'System.FilePath.</>'.
class PathLike p q r | q r -> p, p r -> q, p q -> r where
-- | Like 'System.FilePath.</>', but also supporting symbolic paths.
(</>) :: p -> q -> r
instance PathLike FilePath FilePath FilePath where
(</>) = (FilePath.</>)
-- | This instance ensures we don't accidentally discard a symbolic path
-- in a 'System.FilePath.</>' operation due to the second path being absolute.
--
-- (Recall that @a </> b = b@ whenever @b@ is absolute.)
instance
(b1 ~ 'Dir b2, a3 ~ a1, c2 ~ c3, midAbsolute ~ OnlyRelative)
=> PathLike
(SymbolicPathX allowAbsolute a1 b1)
(SymbolicPathX midAbsolute b2 c2)
(SymbolicPathX allowAbsolute a3 c3)
where
SymbolicPath p1 </> SymbolicPath p2 = SymbolicPath (p1 </> p2)
instance
(b1 ~ 'Dir b2, c2 ~ c3, midAbsolute ~ OnlyRelative)
=> PathLike
(AbsolutePath b1)
(SymbolicPathX midAbsolute b2 c2)
(AbsolutePath c3)
where
AbsolutePath (SymbolicPath p1) </> SymbolicPath p2 =
unsafeMakeAbsolutePath (p1 </> p2)
--------------------------------------------------------------------------------
-- Abstract directory locations.
-- | Abstract directory: current working directory.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data CWD
-- | Abstract directory: package directory (e.g. a directory containing the @.cabal@ file).
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Pkg
-- | Abstract directory: dist directory (e.g. @dist-newstyle@).
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Dist
-- | Abstract directory: source directory (a search directory for source files).
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Source
-- | Abstract directory: include directory (a search directory for CPP includes like header files, e.g. with @ghc -I@).
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Include
-- | Abstract directory: search directory for extra libraries.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Lib
-- | Abstract directory: MacOS framework directory.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Framework
-- | Abstract directory: build directory.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Build
-- | Abstract directory: directory for build artifacts, such as documentation or @.hie@ files.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Artifacts
-- | Abstract directory: package database directory.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data PkgDB
-- | Abstract directory: data files directory.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data DataDir
-- | Abstract directory: directory for HPC @.mix@ files.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Mix
-- | Abstract directory: directory for HPC @.tix@ files.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Tix
-- | Abstract directory: a temporary directory.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Tmp
-- | Abstract directory: directory for response files.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data Response
-- | Abstract directory: directory for pkg-config files.
--
-- See Note [Symbolic paths] in Distribution.Utils.Path.
data PkgConf