Skip to content

Commit c8a88a3

Browse files
committed
Resolve targets to unit-ids instead of build-info
This is more flexible than giving the user the build-info file directly, since this information is redundant as it is located in plan.json. There we can even express some more conditions. If the target is not a local dependency, the user can check that. If the user needs the build-info, then they can look it up in plan.json.
1 parent fda5ed7 commit c8a88a3

File tree

1 file changed

+133
-118
lines changed

1 file changed

+133
-118
lines changed

cabal-install/src/Distribution/Client/CmdStatus.hs

+133-118
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,35 @@ module Distribution.Client.CmdStatus (
1717
statusCommand, statusAction,
1818
) where
1919

20+
import Control.Monad
21+
( mapM )
2022
import qualified Data.Map as Map
2123

2224
import Prelude ()
2325
import Distribution.Client.Compat.Prelude
2426

25-
import Distribution.Client.DistDirLayout
2627
import Distribution.Client.TargetProblem
2728
import Distribution.Client.CmdErrorMessages
2829
import qualified Distribution.Client.InstallPlan as InstallPlan
2930
import Distribution.Client.NixStyleOptions
3031
( NixStyleFlags (..), nixStyleOptions, defaultNixStyleFlags )
3132
import Distribution.Client.ProjectOrchestration
3233
import Distribution.Client.ProjectPlanning
33-
import Distribution.Client.ProjectPlanning.Types
3434
import Distribution.Client.Setup
3535
( GlobalFlags, ConfigFlags(..), yesNoOpt )
36+
import Distribution.Client.Types
37+
( PackageSpecifier, PackageLocation )
38+
import Distribution.Client.TargetSelector
39+
( TargetSelectorProblem )
3640
import Distribution.Client.Utils.Json
3741
( (.=) )
3842
import qualified Distribution.Client.Utils.Json as Json
3943
import Distribution.Client.Version
4044
( cabalInstallVersion )
41-
import Distribution.InstalledPackageInfo
42-
( InstalledPackageInfo )
43-
import Distribution.Parsec (parsecCommaList, parsecToken)
45+
46+
import qualified Distribution.Compat.CharParsing as P
4447
import Distribution.ReadE
4548
( ReadE(ReadE), parsecToReadE )
46-
import Distribution.Simple.BuildPaths (buildInfoPref)
4749
import Distribution.Simple.Command
4850
( CommandUI(..), option, reqArg, ShowOrParseArgs, OptionField )
4951
import Distribution.Simple.Compiler
@@ -52,6 +54,8 @@ import Distribution.Simple.Flag
5254
( Flag(..), fromFlagOrDefault )
5355
import Distribution.Simple.Utils
5456
( wrapText, die', withOutputMarker, ordNub )
57+
import Distribution.Solver.Types.SourcePackage
58+
import Distribution.Types.UnitId
5559
import Distribution.Verbosity
5660
( normal )
5761
import Distribution.Version
@@ -68,14 +72,14 @@ statusCommand = CommandUI
6872
"Query for available targets and project information such as project GHC."
6973
, commandNotes = Just $ \pname ->
7074
"Examples:\n"
71-
++ " " ++ pname ++ " status --output-format=json --compiler-info\n"
75+
++ " " ++ pname ++ " status --output-format=json --compiler\n"
7276
++ " Print the compiler that is used for this project in the json format.\n"
73-
++ " " ++ pname ++ " status --output-format=json --build-info=./src/Foo.hs\n"
74-
++ " Print the location of the component \"src/Foo.hs\" belongs to.\n"
75-
++ " " ++ pname ++ " status --output-format=json --build-info=./src/Foo.hs\n"
76-
++ " Print both, compiler information and build-info location for the given target.\n"
77-
++ " " ++ pname ++ " status --output-format=json --build-info=./src/Foo.hs --build-info=./test/Bar.hs\n"
78-
++ " Print build-info location for multiple targets.\n"
77+
++ " " ++ pname ++ " status --output-format=json --target=./src/Foo.hs\n"
78+
++ " Print the unit-id of the component \"src/Foo.hs\" belongs to.\n"
79+
++ " " ++ pname ++ " status --output-format=json --target=./src/Foo.hs\n"
80+
++ " Print both, compiler information and unit-id for the given target.\n"
81+
++ " " ++ pname ++ " status --output-format=json --target=./src/Foo.hs --target=./test/Bar.hs\n"
82+
++ " Print unit-id location for multiple targets.\n"
7983
, commandUsage = \pname ->
8084
"Usage: " ++ pname ++ " status [FLAGS]\n"
8185
, commandDefaultFlags = defaultNixStyleFlags defaultStatusFlags
@@ -92,14 +96,14 @@ data StatusOutputFormat
9296
deriving (Eq, Ord, Show, Read)
9397

9498
data StatusFlags = StatusFlags
95-
{ statusBuildInfo :: [String]
99+
{ statusTargets :: [String]
96100
, statusCompiler :: Flag Bool
97101
, statusOutputFormat :: Flag StatusOutputFormat
98102
} deriving (Eq, Show, Read)
99103

100104
defaultStatusFlags :: StatusFlags
101105
defaultStatusFlags = StatusFlags
102-
{ statusBuildInfo = mempty
106+
{ statusTargets = mempty
103107
, statusCompiler = mempty
104108
, statusOutputFormat = mempty
105109
}
@@ -125,23 +129,22 @@ statusOptions showOrParseArgs =
125129
statusOutputFormatParser
126130
statusOutputFormatPrinter
127131
)
128-
, option [] ["build-info"]
129-
"List all available targets in the project"
130-
statusBuildInfo (\v flags -> flags { statusBuildInfo = v ++ statusBuildInfo flags})
131-
(reqArg "TARGET" buildInfoTargetReadE (fmap show))
132-
, option [] ["compiler-info"]
132+
, option [] ["target"]
133+
"Given a target, obtain the unit-id in the build-plan"
134+
statusTargets (\v flags -> flags { statusTargets = v ++ statusTargets flags})
135+
(reqArg "TARGET" buildInfoTargetReadE id)
136+
, option [] ["compiler"]
133137
"Print information of the project compiler"
134138
statusCompiler (\v flags -> flags { statusCompiler = v })
135139
(yesNoOpt showOrParseArgs)
136140
]
137141
where
138142
buildInfoTargetReadE :: ReadE [String]
139143
buildInfoTargetReadE =
140-
parsecToReadE
144+
fmap pure $ parsecToReadE
141145
-- This error should never be shown
142146
("couldn't parse targets: " ++)
143-
-- TODO: wrong parser, kills filepaths with spaces
144-
(parsecCommaList parsecToken)
147+
(P.munch1 (const True))
145148

146149
-------------------------------------------------------------------------------
147150
-- Action
@@ -163,49 +166,112 @@ statusAction flags@NixStyleFlags { extraFlags = statusFlags, ..} cliTargetString
163166
(projectConfig baseCtx)
164167
(localPackages baseCtx)
165168

166-
let initialJson = Json.object
167-
[ "cabal-version" .= jdisplay cabalInstallVersion
168-
]
169-
170-
compilerJson <- if not $ fromFlagOrDefault False (statusCompiler statusFlags)
171-
then pure $ Json.object [] -- Neutral element
169+
compilerInformation <- if not $ fromFlagOrDefault False (statusCompiler statusFlags)
170+
then pure Nothing
172171
else do
173172
let compiler = pkgConfigCompiler elabSharedConfig
174173
compilerProg <- requireCompilerProg verbosity compiler
175174
let progDb = pkgConfigCompilerProgs elabSharedConfig
176175
(configuredCompilerProg, _) <- requireProgram verbosity compilerProg progDb
177-
pure $ mkCompilerInfo configuredCompilerProg compiler
176+
pure $ Just $ mkCompilerInfo configuredCompilerProg compiler
178177

179-
buildInfoJson <- if null (statusBuildInfo statusFlags)
180-
then pure $ Json.object [] -- Neutral element
178+
resolvedTargets <- if null (statusTargets statusFlags)
179+
then pure Nothing
181180
else do
182-
let targetStrings = statusBuildInfo statusFlags
183-
targetSelectors <- readTargetSelectors (localPackages baseCtx) Nothing targetStrings >>= \case
184-
Left err -> reportTargetSelectorProblems verbosity err
185-
Right sels -> pure sels
186-
181+
let targetStrings = statusTargets statusFlags
182+
mtargetSelectors <- mapM (readTargetSelector (localPackages baseCtx) Nothing) targetStrings
183+
let (unresolvable, targetSelectors) = partitionEithers
184+
$ map (\(mts, str) -> case mts of
185+
Left _ -> Left str
186+
Right ts -> Right (ts, str)
187+
)
188+
$ zip mtargetSelectors targetStrings
187189
-- Interpret the targets on the command line as build targets
188190
-- (as opposed to say repl or haddock targets).
189191
-- TODO: don't throw on targets that are invalid.
192+
-- TODO: why might this still fail? should we try to avoid that?
190193
targets <- either (reportBuildTargetProblems verbosity) return
191194
$ resolveTargets
192195
selectPackageTargets
193196
selectComponentTarget
194197
elaboratedPlan
195198
Nothing
196-
targetSelectors
199+
(map fst targetSelectors)
197200

198-
pure $ mkBuildInfoJson (distDirLayout baseCtx) elabSharedConfig
199-
elaboratedPlan targets targetSelectors targetStrings
201+
pure $ Just $ mkBuildInfoJson elaboratedPlan targets (Map.fromList targetSelectors) unresolvable
200202

201-
let statusJson = mergeJsonObjects [initialJson, compilerJson, buildInfoJson]
203+
let si = StatusInformation
204+
{ siCabalVersion = cabalInstallVersion
205+
, siCompiler = compilerInformation
206+
, siTargetResolving = resolvedTargets
207+
}
208+
209+
serialisedStatusInformation <- serialise verbosity (statusOutputFormat statusFlags) si
202210

203211
-- Final output
204-
putStrLn $ withOutputMarker verbosity $ Json.encodeToString statusJson
212+
putStrLn $ withOutputMarker verbosity serialisedStatusInformation
205213
where
206214
verbosity = fromFlagOrDefault normal (configVerbosity configFlags)
207215
cliConfig = commandLineFlagsToProjectConfig globalFlags flags mempty
208216

217+
-- ----------------------------------------------------------------------------
218+
-- Big Datatype that can be serialised to different formats
219+
-- ----------------------------------------------------------------------------
220+
221+
data StatusInformation = StatusInformation
222+
{ siCabalVersion :: Version
223+
, siCompiler :: Maybe CompilerInformation
224+
, siTargetResolving :: Maybe [ResolvedTarget]
225+
}
226+
deriving (Show, Read, Eq, Ord)
227+
228+
data CompilerInformation = CompilerInformation
229+
{ ciFlavour :: CompilerFlavor
230+
, ciCompilerId :: CompilerId
231+
, ciPath :: FilePath
232+
}
233+
deriving (Show, Read, Eq, Ord)
234+
235+
data ResolvedTarget = ResolvedTarget
236+
{ rtOriginalTarget :: String
237+
-- | UnitId of the resolved target.
238+
-- If 'Nothing', then the given target can not be resolved
239+
-- to a target in this project.
240+
, rtUnitId :: Maybe UnitId
241+
}
242+
deriving (Show, Read, Eq, Ord)
243+
244+
serialise :: Verbosity -> Flag StatusOutputFormat -> StatusInformation -> IO String
245+
serialise verbosity NoFlag _ =
246+
die' verbosity $ "Could not serialise Status information. "
247+
++ "The flag '--output-format' is required."
248+
249+
serialise _ (Flag JSON) si = pure $ Json.encodeToString $ Json.object $
250+
[ "cabal-version" .= jdisplay (siCabalVersion si)
251+
]
252+
++ prettyCompilerInfo (siCompiler si)
253+
++ prettyTargetResolving (siTargetResolving si)
254+
where
255+
prettyCompilerInfo Nothing = []
256+
prettyCompilerInfo (Just ci) =
257+
[ "compiler" .= Json.object
258+
[ "flavour" .= jdisplay (ciFlavour ci)
259+
, "compiler-id" .= jdisplay (ciCompilerId ci)
260+
, "path" .= Json.String (ciPath ci)
261+
]
262+
]
263+
264+
prettyTargetResolving Nothing = []
265+
prettyTargetResolving (Just rts) =
266+
[ "targets" .= Json.Array (fmap prettyResolvedTarget rts)
267+
]
268+
where
269+
prettyResolvedTarget rt = Json.object
270+
[ "target" .= Json.String (rtOriginalTarget rt)
271+
, "unit-id" .= maybe Json.Null jdisplay (rtUnitId rt)
272+
]
273+
274+
209275
-- ----------------------------------------------------------------------------
210276
-- Helpers for determining and serialising compiler information
211277
-- ----------------------------------------------------------------------------
@@ -219,83 +285,29 @@ requireCompilerProg verbosity compiler =
219285
"status: Unsupported compiler flavour: "
220286
<> prettyShow flavour
221287

222-
mkCompilerInfo :: ConfiguredProgram -> Compiler -> Json.Value
288+
mkCompilerInfo :: ConfiguredProgram -> Compiler -> CompilerInformation
223289
mkCompilerInfo compilerProgram compiler =
224-
Json.object
225-
[ "compiler" .= Json.object
226-
[ "flavour" .= Json.String (prettyShow $ compilerFlavor compiler)
227-
, "compiler-id" .= Json.String (showCompilerId compiler)
228-
, "path" .= Json.String (programPath compilerProgram)
229-
]
230-
]
290+
CompilerInformation (compilerFlavor compiler) (compilerId compiler) (programPath compilerProgram)
231291

232292
-- ----------------------------------------------------------------------------
233-
-- Helpers for determining and serialising build info
293+
-- Helpers for determining and serialising the unit-id
234294
-- ----------------------------------------------------------------------------
235295

236-
mkBuildInfoJson :: DistDirLayout -> ElaboratedSharedConfig -> ElaboratedInstallPlan -> TargetsMap -> [TargetSelector] -> [String] -> Json.Value
237-
mkBuildInfoJson distDirLayout elaboratedSharedConfig elaboratedPlan targetsMap targetSelectors targetStrings = Json.object
238-
[ "build-info" .= Json.Array allTargetsJsons
296+
mkBuildInfoJson :: ElaboratedInstallPlan -> TargetsMap -> Map TargetSelector String -> [String] -> [ResolvedTarget]
297+
mkBuildInfoJson elaboratedPlan targetsMap tsMap unresolvableTargetStrings =
298+
[ ResolvedTarget str (Just uid)
299+
| uid <- Map.keys subsetInstallPlan
300+
, (_, tss) <- targetsMap Map.! uid
301+
, str <- ordNub $ map tsToOriginalTarget $ toList tss
239302
]
303+
++ map mkUnresolvedTarget unresolvableTargetStrings
240304
where
241-
allTargetsJsons =
242-
[ planPackageToJ elab ts
243-
| (uid, elab) <- Map.assocs subsetInstallPlan
244-
, (_, tss) <- targetsMap Map.! uid
245-
, ts <- ordNub $ toList tss
246-
]
247-
248305
subsetInstallPlan = Map.restrictKeys (InstallPlan.toMap elaboratedPlan) (Map.keysSet targetsMap)
249306

250-
targetsTable = Map.fromList $ zip targetSelectors targetStrings
251-
252-
tsToOriginalTarget ts = targetsTable Map.! ts
253-
254-
planPackageToJ :: ElaboratedPlanPackage -> TargetSelector -> Json.Value
255-
planPackageToJ pkg ts =
256-
case pkg of
257-
InstallPlan.PreExisting ipi -> installedPackageInfoToJ ipi
258-
InstallPlan.Configured elab -> elaboratedPackageToJ elab ts
259-
InstallPlan.Installed elab -> elaboratedPackageToJ elab ts
260-
-- Note that the --build-info currently only uses the elaborated plan,
261-
-- not the improved plan. So we will not get the Installed state for
262-
-- that case, but the code supports it in case we want to use this
263-
-- later in some use case where we want the status of the build.
264-
265-
-- TODO: what should we do if we run in this case?
266-
-- Happens on `--build-info=containers` while we are not in the containers project.
267-
installedPackageInfoToJ :: InstalledPackageInfo -> Json.Value
268-
installedPackageInfoToJ _ipi =
269-
-- Pre-existing packages lack configuration information such as their flag
270-
-- settings or non-lib components. We only get pre-existing packages for
271-
-- the global/core packages however, so this isn't generally a problem.
272-
-- So these packages are never local to the project.
273-
--
274-
Json.object []
275-
276-
elaboratedPackageToJ :: ElaboratedConfiguredPackage -> TargetSelector -> Json.Value
277-
elaboratedPackageToJ elab ts = Json.object
278-
[ "target" .= Json.String (tsToOriginalTarget ts)
279-
, "path" .= maybe Json.Null Json.String buildInfoFileLocation
280-
]
281-
where
282-
dist_dir :: FilePath
283-
dist_dir = distBuildDirectory distDirLayout
284-
(elabDistDirParams elaboratedSharedConfig elab)
285-
286-
-- | Only add build-info file location if the Setup.hs CLI
287-
-- is recent enough to be able to generate build info files.
288-
-- Otherwise, write 'null'.
289-
--
290-
-- Consumers of `status` can use the nullability of this file location
291-
-- to indicate that the given component uses `build-type: Custom`
292-
-- with an old lib:Cabal version.
293-
buildInfoFileLocation :: Maybe FilePath
294-
buildInfoFileLocation
295-
| elabSetupScriptCliVersion elab < mkVersion [3, 7, 0, 0]
296-
= Nothing
297-
| otherwise
298-
= Just (buildInfoPref dist_dir)
307+
tsToOriginalTarget ts = tsMap Map.! ts
308+
309+
mkUnresolvedTarget :: String -> ResolvedTarget
310+
mkUnresolvedTarget s = ResolvedTarget s Nothing
299311

300312
-- ----------------------------------------------------------------------------
301313
-- Target selectors and helpers
@@ -350,17 +362,20 @@ reportBuildTargetProblems :: Verbosity -> [TargetProblem'] -> IO a
350362
reportBuildTargetProblems verbosity problems =
351363
reportTargetProblems verbosity "status" problems
352364

365+
readTargetSelector :: [PackageSpecifier (SourcePackage (PackageLocation a))]
366+
-> Maybe ComponentKindFilter
367+
-> String
368+
-> IO (Either TargetSelectorProblem TargetSelector)
369+
readTargetSelector pkgs mfilter targetStr =
370+
readTargetSelectors pkgs mfilter [targetStr] >>= \case
371+
Left [problem] -> pure $ Left problem
372+
Right [ts] -> pure $ Right ts
373+
_ -> error $ "CmdStatus.readTargetSelector: invariant broken, more than "
374+
++ "one target passed *somehow*."
375+
353376
-- ----------------------------------------------------------------------------
354377
-- JSON serialisation helpers
355378
-- ----------------------------------------------------------------------------
356379

357380
jdisplay :: Pretty a => a -> Json.Value
358381
jdisplay = Json.String . prettyShow
359-
360-
mergeJsonObjects :: [Json.Value] -> Json.Value
361-
mergeJsonObjects = Json.object . foldl' go []
362-
where
363-
go acc (Json.Object objs) =
364-
acc <> objs
365-
go _ _ =
366-
error "mergeJsonObjects: Only objects can be merged"

0 commit comments

Comments
 (0)