1 {-# LANGUAGE BangPatterns #-}
2 {-# LANGUAGE DeriveDataTypeable #-}
4 -- | This module implements a view of a 'VersionRange' as a finite
5 -- list of separated version intervals.
7 -- In conversion from and to 'VersionRange' it makes some effort to
8 -- preserve the caret operator @^>=x.y@. This constraint a priori
9 -- specifies the same interval as @==x.y.*@, but indicates that newer
10 -- versions could be acceptable (@allow-newer: ^@).
11 module Distribution
.Types
.VersionInterval
12 ( -- * Version intervals
19 , fromVersionIntervals
22 , normaliseVersionRange2
28 -- * Version intervals view
30 , VersionInterval
(..)
36 , invariantVersionIntervals
39 import Control
.Applicative
(liftA2
)
40 import Control
.Exception
(assert
)
41 import Distribution
.Compat
.Prelude
hiding (Applicative
(..))
44 import Distribution
.Types
.Version
45 import Distribution
.Types
.VersionRange
.Internal
47 -- To test this module, and to run version range normalisation benchmarks:
49 -- cabal run Cabal-tests:unit-tests -- -p Distribution.Version
50 -- cabal run cabal-benchmarks -- -o bench.html normaliseVersionRange
52 -------------------------------------------------------------------------------
54 -------------------------------------------------------------------------------
56 -- | A complementary representation of a 'VersionRange'. Instead of a boolean
57 -- version predicate it uses an increasing sequence of non-overlapping,
58 -- non-empty intervals.
60 -- The key point is that this representation gives a canonical representation
61 -- for the semantics of 'VersionRange's. This makes it easier to check things
62 -- like whether a version range is empty, covers all versions, or requires a
63 -- certain minimum or maximum version. It also makes it easy to check equality
64 -- or containment. It also makes it easier to identify \'simple\' version
65 -- predicates for translation into foreign packaging systems that do not
66 -- support complex version range expressions.
67 newtype VersionIntervals
= VersionIntervals
[VersionInterval
]
68 deriving (Eq
, Show, Typeable
)
70 -- | Inspect the list of version intervals.
71 unVersionIntervals
:: VersionIntervals
-> [VersionInterval
]
72 unVersionIntervals
(VersionIntervals is
) = is
74 -- | Directly construct a 'VersionIntervals' from a list of intervals.
75 mkVersionIntervals
:: [VersionInterval
] -> Maybe VersionIntervals
76 mkVersionIntervals intervals
77 | invariantVersionIntervals
(VersionIntervals intervals
) = Just
. VersionIntervals
$ intervals
80 data VersionInterval
= VersionInterval
!LowerBound
!UpperBound
deriving (Eq
, Show)
81 data LowerBound
= LowerBound
!Version
!Bound
deriving (Eq
, Show)
82 data UpperBound
= NoUpperBound | UpperBound
!Version
!Bound
deriving (Eq
, Show)
83 data Bound
= ExclusiveBound | InclusiveBound
deriving (Eq
, Show)
85 zeroLowerBound
:: LowerBound
86 zeroLowerBound
= LowerBound version0 InclusiveBound
88 isVersion0
:: Version
-> Bool
89 isVersion0
= (==) version0
91 -------------------------------------------------------------------------------
93 -------------------------------------------------------------------------------
95 stage1
:: VersionRange
-> [VersionInterval
]
96 stage1
= cataVersionRange alg
98 -- version range leafs transform into singleton intervals
99 alg
(ThisVersionF v
) = [VersionInterval
(LowerBound v InclusiveBound
) (UpperBound v InclusiveBound
)]
100 alg
(LaterVersionF v
) = [VersionInterval
(LowerBound v ExclusiveBound
) NoUpperBound
]
101 alg
(OrLaterVersionF v
) = [VersionInterval
(LowerBound v InclusiveBound
) NoUpperBound
]
102 alg
(EarlierVersionF v
)
104 |
otherwise = [VersionInterval zeroLowerBound
(UpperBound v ExclusiveBound
)]
105 alg
(OrEarlierVersionF v
) = [VersionInterval zeroLowerBound
(UpperBound v InclusiveBound
)]
106 -- \^>= version-range's upper bound should be MajorBound
107 alg
(MajorBoundVersionF v
) = [VersionInterval
(LowerBound v InclusiveBound
) (UpperBound
(majorUpperBound v
) ExclusiveBound
)]
108 -- union: just merge the version intervals
109 alg
(UnionVersionRangesF v1 v2
) = v1
++ v2
110 -- intersection: pairwise intersect. Strip empty intervals. Sort to restore the invariant.
111 alg
(IntersectVersionRangesF v1 v2
) = mapMaybe nonEmptyInterval
$ liftA2 intersectInterval
(stage2and3 v1
) (stage2and3 v2
)
113 -- | Check that interval is non-empty
114 nonEmptyInterval
:: VersionInterval
-> Maybe VersionInterval
115 nonEmptyInterval i | nonEmptyVI i
= Just i
116 nonEmptyInterval _
= Nothing
118 -------------------------------------------------------------------------------
120 -------------------------------------------------------------------------------
122 stage2
:: [VersionInterval
] -> [VersionInterval
]
123 stage2
= sortBy lowerboundCmp
125 lowerboundCmp
:: VersionInterval
-> VersionInterval
-> Ordering
126 lowerboundCmp
(VersionInterval
(LowerBound v vb
) _
) (VersionInterval
(LowerBound u ub
) _
) =
127 compare v u `mappend` compareBound vb ub
129 compareBound
:: Bound
-> Bound
-> Ordering
130 compareBound InclusiveBound InclusiveBound
= EQ
131 compareBound InclusiveBound ExclusiveBound
= LT
132 compareBound ExclusiveBound InclusiveBound
= GT
133 compareBound ExclusiveBound ExclusiveBound
= EQ
135 -------------------------------------------------------------------------------
137 -------------------------------------------------------------------------------
139 -- | Post-processing takes a list of ordered version intervals,
140 -- but possibly overlapping, and creates 'VersionIntervals'.
141 postprocess
:: [VersionInterval
] -> VersionIntervals
142 postprocess
= checkInvariant
. VersionIntervals
. stage2and3
144 stage2and3
:: [VersionInterval
] -> [VersionInterval
]
145 stage2and3
= stage3
. stage2
147 stage3
:: [VersionInterval
] -> [VersionInterval
]
149 stage3
(VersionInterval lb ub
: rest
) = stage3go lb ub rest
151 stage3go
:: LowerBound
-> UpperBound
-> [VersionInterval
] -> [VersionInterval
]
152 stage3go
!lb NoUpperBound _
= [VersionInterval lb NoUpperBound
]
153 stage3go
!lb
!ub
[] = [VersionInterval lb ub
]
154 stage3go
!lb
!ub
(VersionInterval lb
' ub
' : rest
')
155 | doesNotTouch ub lb
' = VersionInterval lb ub
: stage3go lb
' ub
' rest
'
156 |
otherwise = stage3go lb
(unionUpper ub ub
') rest
'
158 -------------------------------------------------------------------------------
160 -------------------------------------------------------------------------------
162 intersectInterval
:: VersionInterval
-> VersionInterval
-> VersionInterval
163 intersectInterval
(VersionInterval lv uv
) (VersionInterval lu uu
) =
164 VersionInterval
(intersectLower lv lu
) (intersectUpper uv uu
)
166 intersectLower
:: LowerBound
-> LowerBound
-> LowerBound
167 intersectLower
(LowerBound v vb
) (LowerBound u ub
) = case compare v u
of
168 EQ
-> LowerBound v
(intersectBound vb ub
)
169 LT
-> LowerBound u ub
170 GT
-> LowerBound v vb
172 intersectUpper
:: UpperBound
-> UpperBound
-> UpperBound
173 intersectUpper NoUpperBound b
= b
174 intersectUpper b NoUpperBound
= b
175 intersectUpper
(UpperBound v vb
) (UpperBound u ub
) = case compare v u
of
176 EQ
-> UpperBound v
(intersectBound vb ub
)
177 LT
-> UpperBound v vb
178 GT
-> UpperBound u ub
180 intersectBound
:: Bound
-> Bound
-> Bound
181 intersectBound InclusiveBound InclusiveBound
= InclusiveBound
182 intersectBound _ _
= ExclusiveBound
184 -------------------------------------------------------------------------------
186 -------------------------------------------------------------------------------
188 unionUpper
:: UpperBound
-> UpperBound
-> UpperBound
189 unionUpper NoUpperBound _
= NoUpperBound
190 unionUpper _ NoUpperBound
= NoUpperBound
191 unionUpper
(UpperBound v vb
) (UpperBound u ub
) = case compare v u
of
192 EQ
-> UpperBound v
(unionBound vb ub
)
193 LT
-> UpperBound u ub
194 GT
-> UpperBound v vb
196 unionBound
:: Bound
-> Bound
-> Bound
197 unionBound ExclusiveBound ExclusiveBound
= ExclusiveBound
198 unionBound _ _
= InclusiveBound
200 -------------------------------------------------------------------------------
202 -------------------------------------------------------------------------------
204 -- | View a 'VersionRange' as a union of intervals.
206 -- This provides a canonical view of the semantics of a 'VersionRange' as
207 -- opposed to the syntax of the expression used to define it. For the syntactic
208 -- view use 'foldVersionRange'.
210 -- Each interval is non-empty. The sequence is in increasing order and no
211 -- intervals overlap or touch. Therefore only the first and last can be
212 -- unbounded. The sequence can be empty if the range is empty
213 -- (e.g. a range expression like @< 1 && > 2@).
215 -- Other checks are trivial to implement using this view. For example:
217 -- > isNoVersion vr | [] <- asVersionIntervals vr = True
218 -- > | otherwise = False
220 -- > isSpecificVersion vr
221 -- > | [(LowerBound v InclusiveBound
222 -- > ,UpperBound v' InclusiveBound)] <- asVersionIntervals vr
223 -- > , v == v' = Just v
224 -- > | otherwise = Nothing
225 asVersionIntervals
:: VersionRange
-> [VersionInterval
]
226 asVersionIntervals
= unVersionIntervals
. toVersionIntervals
228 -------------------------------------------------------------------------------
230 -------------------------------------------------------------------------------
232 -- | Check an upper bound does not intersect, or even touch a lower bound:
235 -- ---| or ---) but not ---] or ---) or ---]
236 -- |--- (--- (--- [--- [---
238 doesNotTouch
:: UpperBound
-> LowerBound
-> Bool
239 doesNotTouch NoUpperBound _
= False
240 doesNotTouch
(UpperBound u ub
) (LowerBound l lb
) =
241 (u
< l
) ||
(u
== l
&& ub
== ExclusiveBound
&& lb
== ExclusiveBound
)
243 -------------------------------------------------------------------------------
245 -------------------------------------------------------------------------------
247 -- | 'VersionIntervals' invariant:
249 -- * all intervals are valid (lower bound is less then upper bound, i.e. non-empty)
250 -- * intervals doesn't touch each other (distinct)
251 invariantVersionIntervals
:: VersionIntervals
-> Bool
252 invariantVersionIntervals
(VersionIntervals intervals
) =
253 all validInterval intervals
254 && all doesNotTouch
' adjacentIntervals
256 doesNotTouch
' :: (VersionInterval
, VersionInterval
) -> Bool
257 doesNotTouch
' (VersionInterval _ u
, VersionInterval l
' _
) = doesNotTouch u l
'
259 adjacentIntervals
:: [(VersionInterval
, VersionInterval
)]
260 adjacentIntervals
= case intervals
of
262 (_
: tl
) -> zip intervals tl
264 checkInvariant
:: VersionIntervals
-> VersionIntervals
265 checkInvariant is
= assert
(invariantVersionIntervals is
) is
266 {-# INLINE checkInvariant #-}
268 validInterval
:: VersionInterval
-> Bool
269 validInterval i
@(VersionInterval l u
) = validLower l
&& validUpper u
&& nonEmptyVI i
271 validLower
(LowerBound v _
) = validVersion v
272 validUpper NoUpperBound
= True
273 validUpper
(UpperBound v _
) = validVersion v
275 -- Check an interval is non-empty
277 nonEmptyVI
:: VersionInterval
-> Bool
278 nonEmptyVI
(VersionInterval _ NoUpperBound
) = True
279 nonEmptyVI
(VersionInterval
(LowerBound l lb
) (UpperBound u ub
)) =
280 (l
< u
) ||
(l
== u
&& lb
== InclusiveBound
&& ub
== InclusiveBound
)
282 -------------------------------------------------------------------------------
284 -------------------------------------------------------------------------------
286 -- | Convert a 'VersionRange' to a sequence of version intervals.
287 toVersionIntervals
:: VersionRange
-> VersionIntervals
288 toVersionIntervals
= postprocess
. stage1
290 -- | Convert a 'VersionIntervals' value back into a 'VersionRange' expression
291 -- representing the version intervals.
292 fromVersionIntervals
:: VersionIntervals
-> VersionRange
293 fromVersionIntervals
(VersionIntervals
[]) = noVersion
294 fromVersionIntervals
(VersionIntervals
(x
: xs
)) = foldr1 unionVersionRanges
(fmap intervalToVersionRange
(x
:| xs
))
296 intervalToVersionRange
:: VersionInterval
-> VersionRange
297 intervalToVersionRange
(VersionInterval
(LowerBound v vb
) upper
') = case upper
' of
301 | vb
== InclusiveBound
302 , ub
== InclusiveBound
305 UpperBound u ub
-> withLowerBound
(makeUpperBound u ub
)
307 lowerBound
:: VersionRange
308 lowerBound
= case vb
of
309 InclusiveBound
-> orLaterVersion v
310 ExclusiveBound
-> laterVersion v
312 withLowerBound
:: VersionRange
-> VersionRange
314 | isVersion0 v
, vb
== InclusiveBound
= vr
315 |
otherwise = intersectVersionRanges lowerBound vr
317 makeUpperBound
:: Version
-> Bound
-> VersionRange
318 makeUpperBound u InclusiveBound
= orEarlierVersion u
319 makeUpperBound u ExclusiveBound
= earlierVersion u
321 -------------------------------------------------------------------------------
323 -------------------------------------------------------------------------------
325 -- | Since @Cabal-3.6@ this function.. TODO
326 normaliseVersionRange2
:: VersionRange
-> VersionRange
327 normaliseVersionRange2
= fromVersionIntervals
. toVersionIntervals
329 -------------------------------------------------------------------------------
331 -------------------------------------------------------------------------------
333 relaxLastInterval
:: VersionIntervals
-> VersionIntervals
334 relaxLastInterval
(VersionIntervals xs
) = VersionIntervals
(relaxLastInterval
' xs
)
336 relaxLastInterval
' [] = []
337 relaxLastInterval
' [VersionInterval l _
] = [VersionInterval l NoUpperBound
]
338 relaxLastInterval
' (i
: is
) = i
: relaxLastInterval
' is
340 relaxHeadInterval
:: VersionIntervals
-> VersionIntervals
341 relaxHeadInterval
(VersionIntervals xs
) = VersionIntervals
(relaxHeadInterval
' xs
)
343 relaxHeadInterval
' [] = []
344 relaxHeadInterval
' (VersionInterval _ u
: is
) = VersionInterval zeroLowerBound u
: is