Skip to content

Commit f140135

Browse files
committed
Update UnitId
This patch is quite raw. - Effectively ComponentId and UnitId are very similar. I think UnitId should just be a newtype around ComponentId. - We have Partial IDs, which should really be called Legacy, as they deal with non-compiler-prefixed ids. - We need to modify the parsing of installed packages, to inject the compiler into unit-ids. - We also need to modify parsing configure flags (--dependency=...) to ensure the ids are aligned. Note: we need to support INTERNAL and EXTERNAL Setup.hs. (This shows up with --dependency=...) especially.
1 parent a1494a8 commit f140135

File tree

19 files changed

+248
-109
lines changed

19 files changed

+248
-109
lines changed

Cabal-syntax/src/Distribution/Backpack.hs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ import Distribution.Utils.Base62
5353
import qualified Data.Map as Map
5454
import qualified Data.Set as Set
5555

56+
import GHC.Stack (HasCallStack)
57+
58+
import Unsafe.Coerce (unsafeCoerce)
59+
5660
-----------------------------------------------------------------------
5761
-- OpenUnitId
5862

@@ -147,9 +151,7 @@ mkOpenUnitId uid cid insts =
147151
mkDefUnitId :: ComponentId -> Map ModuleName Module -> DefUnitId
148152
mkDefUnitId cid insts =
149153
unsafeMkDefUnitId
150-
( mkUnitId
151-
(unComponentId cid ++ maybe "" ("+" ++) (hashModuleSubst insts))
152-
)
154+
(addSuffixToUnitId (maybe "" ("+" ++) (hashModuleSubst insts)) (unsafeCoerce cid))
153155

154156
-- impose invariant!
155157

@@ -254,7 +256,7 @@ openModuleSubstFreeHoles insts = Set.unions (map openModuleFreeHoles (Map.elems
254256
-- | When typechecking, we don't demand that a freshly instantiated
255257
-- 'IndefFullUnitId' be compiled; instead, we just depend on the
256258
-- installed indefinite unit installed at the 'ComponentId'.
257-
abstractUnitId :: OpenUnitId -> UnitId
259+
abstractUnitId :: HasCallStack => OpenUnitId -> UnitId
258260
abstractUnitId (DefiniteUnitId def_uid) = unDefUnitId def_uid
259261
abstractUnitId (IndefFullUnitId cid _) = newSimpleUnitId cid
260262

Cabal-syntax/src/Distribution/InstalledPackageInfo.hs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,15 @@ import qualified Text.PrettyPrint as Disp
6262
import Distribution.Types.InstalledPackageInfo
6363
import Distribution.Types.InstalledPackageInfo.FieldGrammar
6464

65-
installedComponentId :: InstalledPackageInfo -> ComponentId
65+
import GHC.Stack (HasCallStack)
66+
67+
import Unsafe.Coerce (unsafeCoerce)
68+
69+
installedComponentId :: HasCallStack => InstalledPackageInfo -> ComponentId
6670
installedComponentId ipi =
67-
case unComponentId (installedComponentId_ ipi) of
68-
"" -> mkComponentId (unUnitId (installedUnitId ipi))
69-
_ -> installedComponentId_ ipi
71+
fromMaybe
72+
(unsafeCoerce (installedUnitId ipi))
73+
(installedComponentId_ ipi)
7074

7175
-- | Get the indefinite unit identity representing this package.
7276
-- This IS NOT guaranteed to give you a substitution; for
@@ -117,20 +121,20 @@ parseInstalledPackageInfo s = case P.readFields s of
117121
-- | Pretty print 'InstalledPackageInfo'.
118122
--
119123
-- @pkgRoot@ isn't printed, as ghc-pkg prints it manually (as GHC-8.4).
120-
showInstalledPackageInfo :: InstalledPackageInfo -> String
124+
showInstalledPackageInfo :: HasCallStack => InstalledPackageInfo -> String
121125
showInstalledPackageInfo ipi =
122126
showFullInstalledPackageInfo ipi{pkgRoot = Nothing}
123127

124128
-- | The variant of 'showInstalledPackageInfo' which outputs @pkgroot@ field too.
125-
showFullInstalledPackageInfo :: InstalledPackageInfo -> String
129+
showFullInstalledPackageInfo :: HasCallStack => InstalledPackageInfo -> String
126130
showFullInstalledPackageInfo = P.showFields (const NoComment) . prettyFieldGrammar cabalSpecLatest ipiFieldGrammar
127131

128132
-- |
129133
--
130134
-- >>> let ipi = emptyInstalledPackageInfo { maintainer = fromString "Tester" }
131135
-- >>> fmap ($ ipi) $ showInstalledPackageInfoField "maintainer"
132136
-- Just "maintainer: Tester"
133-
showInstalledPackageInfoField :: String -> Maybe (InstalledPackageInfo -> String)
137+
showInstalledPackageInfoField :: HasCallStack => String -> Maybe (InstalledPackageInfo -> String)
134138
showInstalledPackageInfoField fn =
135139
fmap (\g -> Disp.render . ppField fn . g) $ fieldDescrPretty ipiFieldGrammar (toUTF8BS fn)
136140

Cabal-syntax/src/Distribution/Types/ComponentId.hs

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import Distribution.Pretty
1717
import qualified Distribution.Compat.CharParsing as P
1818
import Text.PrettyPrint (text)
1919

20+
import GHC.Stack (HasCallStack)
2021
-- | A 'ComponentId' uniquely identifies the transitive source
2122
-- code closure of a component (i.e. libraries, executables).
2223
--
@@ -30,8 +31,21 @@ import Text.PrettyPrint (text)
3031
-- This type is opaque since @Cabal-2.0@
3132
--
3233
-- @since 2.0.0.2
33-
newtype ComponentId = ComponentId ShortText
34-
deriving (Generic, Read, Show, Eq, Ord, Data)
34+
data ComponentId = ComponentId ShortText ShortText Bool
35+
| PartialComponentId ShortText
36+
deriving (Generic, Read, Show, Data)
37+
38+
instance Eq ComponentId where
39+
(ComponentId c1 s1 _) == (ComponentId c2 s2 _) = c1 == c2 && s1 == s2
40+
(PartialComponentId s1) == (PartialComponentId s2) = s1 == s2
41+
_ == _ = False
42+
43+
instance Ord ComponentId where
44+
compare (ComponentId c1 s1 _) (ComponentId c2 s2 _) = compare (c1, s1) (c2, s2)
45+
compare (PartialComponentId s1) (PartialComponentId s2) = compare s1 s2
46+
compare (PartialComponentId _) _ = LT
47+
compare _ (PartialComponentId _) = GT
48+
3549

3650
-- | Construct a 'ComponentId' from a 'String'
3751
--
@@ -41,14 +55,25 @@ newtype ComponentId = ComponentId ShortText
4155
-- 'ComponentId' is valid
4256
--
4357
-- @since 2.0.0.2
44-
mkComponentId :: String -> ComponentId
45-
mkComponentId = ComponentId . toShortText
58+
mkComponentId :: HasCallStack => String -> ComponentId
59+
mkComponentId s = case (simpleParsec s) of
60+
Just cid@ComponentId{} -> cid
61+
Just cid@PartialComponentId{} -> error $ "mkPartialComponentId: `" ++ s ++ "' is a partial component id, not a full one."
62+
_ -> error $ "Unable to parse PartialComponentId: `" ++ s ++ "'."
63+
64+
mkComponentId' :: HasCallStack => String -> String -> Bool -> ComponentId
65+
mkComponentId' c i b = ComponentId (toShortText c) (toShortText i) b
66+
67+
mkPartialComponentId :: HasCallStack => String -> ComponentId
68+
mkPartialComponentId s = PartialComponentId (toShortText s)
4669

4770
-- | Convert 'ComponentId' to 'String'
4871
--
4972
-- @since 2.0.0.2
50-
unComponentId :: ComponentId -> String
51-
unComponentId (ComponentId s) = fromShortText s
73+
unComponentId :: HasCallStack => ComponentId -> String
74+
unComponentId (ComponentId c s False) = fromShortText c ++ '_':fromShortText s
75+
unComponentId (ComponentId c s True) = fromShortText s
76+
unComponentId (PartialComponentId s) = fromShortText s
5277

5378
-- | 'mkComponentId'
5479
--
@@ -63,8 +88,12 @@ instance Pretty ComponentId where
6388
pretty = text . unComponentId
6489

6590
instance Parsec ComponentId where
66-
parsec = mkComponentId `fmap` P.munch1 abi_char
91+
parsec = P.try (mkComponentId' <$> compid <* P.char '_' <*> P.munch1 abi_char <*> return False)
92+
<|> mkPartialComponentId <$> P.munch1 abi_char
6793
where
94+
compid = (\f v -> f ++ "-" ++ v) <$> P.munch1 isAlpha <* P.char '-' <*> P.munch1 isVerChar
95+
isVerChar :: Char -> Bool
96+
isVerChar c = c `elem` '.':['0'..'9']
6897
abi_char c = isAlphaNum c || c `elem` "-_."
6998

7099
instance NFData ComponentId where

Cabal-syntax/src/Distribution/Types/InstalledPackageInfo.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ data InstalledPackageInfo = InstalledPackageInfo
4242
-- exactly the same as PackageDescription
4343
sourcePackageId :: PackageId
4444
, sourceLibName :: LibraryName
45-
, installedComponentId_ :: ComponentId
45+
, installedComponentId_ :: Maybe ComponentId
4646
, libVisibility :: LibraryVisibility
4747
, installedUnitId :: UnitId
4848
, -- INVARIANT: if this package is definite, OpenModule's
@@ -134,7 +134,7 @@ emptyInstalledPackageInfo =
134134
InstalledPackageInfo
135135
{ sourcePackageId = PackageIdentifier (mkPackageName "") nullVersion Nothing
136136
, sourceLibName = LMainLibName
137-
, installedComponentId_ = mkComponentId ""
137+
, installedComponentId_ = Nothing
138138
, installedUnitId = mkUnitId ""
139139
, instantiatedWith = []
140140
, compatPackageKey = ""

Cabal-syntax/src/Distribution/Types/InstalledPackageInfo/FieldGrammar.hs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import Distribution.Types.InstalledPackageInfo
4040
import qualified Distribution.Types.InstalledPackageInfo.Lens as L
4141
import qualified Distribution.Types.PackageId.Lens as L hiding (pkgCompiler)
4242

43+
import GHC.Stack (HasCallStack)
44+
4345
-- Note: GHC goes nuts and inlines everything,
4446
-- One can see e.g. in -ddump-simpl-stats:
4547
--
@@ -75,7 +77,7 @@ ipiFieldGrammar
7577
, c InstWith
7678
, c SpecLicenseLenient
7779
, c (Identity (Maybe CompilerId))
78-
)
80+
, HasCallStack )
7981
=> g InstalledPackageInfo InstalledPackageInfo
8082
ipiFieldGrammar =
8183
mkInstalledPackageInfo
@@ -86,7 +88,10 @@ ipiFieldGrammar =
8688
-- Very basic fields: name, version, package-name, lib-name and visibility
8789
<@> blurFieldGrammar basic basicFieldGrammar
8890
-- Basic fields
89-
<@> optionalFieldDef "id" L.installedUnitId (mkUnitId "")
91+
-- [note: forced default values]
92+
-- Observe optionalFieldDef fn l x = optionalFieldDefAla fn Identity l x
93+
-- and optionalFieldDefAla will force x.
94+
<@> optionalFieldDef "id" L.installedUnitId (mkUnitId "invalid-invalid")
9095
<@> optionalFieldDefAla "instantiated-with" InstWith L.instantiatedWith []
9196
<@> optionalFieldDefAla "key" CompatPackageKey L.compatPackageKey ""
9297
<@> optionalFieldDefAla "license" SpecLicenseLenient L.license (Left SPDX.NONE)
@@ -135,7 +140,7 @@ ipiFieldGrammar =
135140
-- setMaybePackageId says it can be no-op.
136141
(PackageIdentifier pn _basicVersion _basicCompilerId)
137142
(combineLibraryName ln _basicLibName)
138-
(mkComponentId "") -- installedComponentId_, not in use
143+
Nothing
139144
_basicLibVisibility
140145
where
141146
MungedPackageName pn ln = _basicName

Cabal-syntax/src/Distribution/Types/InstalledPackageInfo/Lens.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ installedUnitId :: Lens' InstalledPackageInfo UnitId
2828
installedUnitId f s = fmap (\x -> s{T.installedUnitId = x}) (f (T.installedUnitId s))
2929
{-# INLINE installedUnitId #-}
3030

31-
installedComponentId_ :: Lens' InstalledPackageInfo ComponentId
31+
installedComponentId_ :: Lens' InstalledPackageInfo (Maybe ComponentId)
3232
installedComponentId_ f s = fmap (\x -> s{T.installedComponentId_ = x}) (f (T.installedComponentId_ s))
3333
{-# INLINE installedComponentId_ #-}
3434

Cabal-syntax/src/Distribution/Types/PackageId.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ instance Structured PackageIdentifier
3838

3939
instance Pretty PackageIdentifier where
4040
pretty (PackageIdentifier n v c)
41-
| Just c' <- c, v == nullVersion = pretty c <<>> Disp.char ':' <<>> pretty n -- if no version, don't show version.
42-
| Just c' <- c = pretty c' <<>> Disp.char ':' <<>> pretty n <<>> Disp.char '-' <<>> pretty v
41+
-- we must never print the compiler, as other tools like hackage-security rely on the Pretty instance
42+
-- | Just c' <- c, v == nullVersion = pretty c <<>> Disp.char ':' <<>> pretty n -- if no version, don't show version.
43+
-- | Just c' <- c = pretty c' <<>> Disp.char ':' <<>> pretty n <<>> Disp.char '-' <<>> pretty v
4344
| v == nullVersion = pretty n
4445
| otherwise = pretty n <<>> Disp.char '-' <<>> pretty v
4546

Cabal-syntax/src/Distribution/Types/UnitId.hs

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ module Distribution.Types.UnitId
77
( UnitId
88
, unUnitId
99
, mkUnitId
10+
, isPartialUnitId
11+
, addPrefixToUnitId
12+
, addSuffixToUnitId
1013
, DefUnitId
1114
, unsafeMkDefUnitId
1215
, unDefUnitId
@@ -27,6 +30,11 @@ import Distribution.Types.PackageId
2730

2831
import Text.PrettyPrint (text)
2932

33+
import GHC.Stack (HasCallStack)
34+
import Data.List (isInfixOf)
35+
36+
import Unsafe.Coerce (unsafeCoerce)
37+
3038
-- | A unit identifier identifies a (possibly instantiated)
3139
-- package/component that can be installed the installed package
3240
-- database. There are several types of components that can be
@@ -63,11 +71,24 @@ import Text.PrettyPrint (text)
6371
-- representation of a UnitId to pass, e.g., as a @-package-id@
6472
-- flag, use the 'display' function, which will work on all
6573
-- versions of Cabal.
66-
newtype UnitId = UnitId ShortText
67-
deriving (Generic, Read, Show, Eq, Ord, Data, NFData)
74+
data UnitId = UnitId ShortText ShortText Bool
75+
| PartialUnitId ShortText
76+
deriving (Generic, Read, Show, Data)
77+
78+
instance Eq UnitId where
79+
(UnitId c1 s1 _) == (UnitId c2 s2 _) = c1 == c2 && s1 == s2
80+
(PartialUnitId s1) == (PartialUnitId s2) = s1 == s2
81+
_ == _ = False
82+
83+
instance Ord UnitId where
84+
compare (UnitId c1 s1 _) (UnitId c2 s2 _) = compare (c1, s1) (c2, s2)
85+
compare (PartialUnitId s1) (PartialUnitId s2) = compare s1 s2
86+
compare (PartialUnitId _) _ = LT
87+
compare _ (PartialUnitId _) = GT
6888

6989
instance Binary UnitId
7090
instance Structured UnitId
91+
instance NFData UnitId
7192

7293
-- | The textual format for 'UnitId' coincides with the format
7394
-- GHC accepts for @-package-id@.
@@ -77,22 +98,54 @@ instance Pretty UnitId where
7798
-- | The textual format for 'UnitId' coincides with the format
7899
-- GHC accepts for @-package-id@.
79100
instance Parsec UnitId where
80-
parsec = mkUnitId <$> P.munch1 isUnitChar
101+
parsec = P.try (mkUnitId' <$> compid <* P.char '_' <*> P.munch1 isUnitChar <*> return False)
102+
<|> (mkPartialUnitId <$> P.munch1 isUnitChar)
81103
where
104+
compid = (\f v -> f ++ "-" ++ v) <$> P.munch1 isAlpha <* P.char '-' <*> P.munch1 isVerChar
105+
isVerChar :: Char -> Bool
106+
isVerChar c = c `elem` '.':['0'..'9']
82107
-- https://gitlab.haskell.org/ghc/ghc/issues/17752
83108
isUnitChar '-' = True
84109
isUnitChar '_' = True
85110
isUnitChar '.' = True
86111
isUnitChar '+' = True
87112
isUnitChar c = isAlphaNum c
88113

114+
isPartialUnitId :: HasCallStack => UnitId -> Bool
115+
isPartialUnitId (PartialUnitId _) = True
116+
isPartialUnitId _ = False
117+
118+
addPrefixToUnitId :: HasCallStack => String -> UnitId -> UnitId
119+
addPrefixToUnitId prefix (PartialUnitId s) = UnitId (toShortText prefix) s True
120+
addPrefixToUnitId prefix uid@(UnitId _ _ _) = error $ "addPrefixToUnitId: UnitId " ++ show uid ++ " already has a prefix; can't add: " ++ prefix
121+
122+
addSuffixToUnitId :: HasCallStack => String -> UnitId -> UnitId
123+
addSuffixToUnitId suffix (UnitId c s fromPartial) = UnitId c (s <> toShortText suffix) fromPartial
124+
addSuffixToUnitId suffix (PartialUnitId s) = PartialUnitId (s <> toShortText suffix)
125+
126+
127+
dropPrefixFromUnitId :: HasCallStack => UnitId -> UnitId
128+
dropPrefixFromUnitId (PartialUnitId s) = PartialUnitId s
129+
dropPrefixFromUnitId (UnitId _c s _fromPartial) = PartialUnitId s
130+
89131
-- | If you need backwards compatibility, consider using 'display'
90132
-- instead, which is supported by all versions of Cabal.
91-
unUnitId :: UnitId -> String
92-
unUnitId (UnitId s) = fromShortText s
133+
unUnitId :: HasCallStack => UnitId -> String
134+
unUnitId (UnitId c s False) = fromShortText c ++ '_':fromShortText s
135+
unUnitId (UnitId c s True) = fromShortText s
136+
unUnitId (PartialUnitId s) = fromShortText s
137+
138+
mkUnitId :: HasCallStack => String -> UnitId
139+
mkUnitId s = case (simpleParsec s) of
140+
Just uid@UnitId{} -> uid
141+
Just uid@PartialUnitId{} -> uid -- error $ "mkUnitId: `" ++ s ++ "' is a partial unit id, not a full one."
142+
_ -> error $ "Unable to parse UnitId: `" ++ s ++ "'."
143+
144+
mkUnitId' :: HasCallStack => String -> String -> Bool -> UnitId
145+
mkUnitId' c i b = UnitId (toShortText c) (toShortText i) b
93146

94-
mkUnitId :: String -> UnitId
95-
mkUnitId = UnitId . toShortText
147+
mkPartialUnitId :: HasCallStack => String -> UnitId
148+
mkPartialUnitId s = PartialUnitId (toShortText s)
96149

97150
-- | 'mkUnitId'
98151
--
@@ -102,17 +155,17 @@ instance IsString UnitId where
102155

103156
-- | Create a unit identity with no associated hash directly
104157
-- from a 'ComponentId'.
105-
newSimpleUnitId :: ComponentId -> UnitId
106-
newSimpleUnitId = mkUnitId . unComponentId
158+
newSimpleUnitId :: HasCallStack => ComponentId -> UnitId
159+
newSimpleUnitId = unsafeCoerce
107160

108161
-- | Make an old-style UnitId from a package identifier.
109162
-- Assumed to be for the public library
110-
mkLegacyUnitId :: PackageId -> UnitId
163+
mkLegacyUnitId :: HasCallStack => PackageId -> UnitId
111164
mkLegacyUnitId = newSimpleUnitId . mkComponentId . prettyShow
112165

113166
-- | Returns library name prefixed with HS, suitable for filenames
114167
getHSLibraryName :: UnitId -> String
115-
getHSLibraryName uid = "HS" ++ prettyShow uid
168+
getHSLibraryName uid = "HS" ++ prettyShow (dropPrefixFromUnitId uid)
116169

117170
-- | A 'UnitId' for a definite package. The 'DefUnitId' invariant says
118171
-- that a 'UnitId' identified this way is definite; i.e., it has no

0 commit comments

Comments
 (0)