Skip to content

Commit 7327c12

Browse files
authored
Merge pull request #4586 from fgaz/new-run-2
Completed the 'new-run' command (#4477). The functionality is the same of the old 'run' command but using nix-style builds. Additionally, it can run executables across packages in a project.
2 parents f303d01 + 81b5f4c commit 7327c12

File tree

31 files changed

+504
-38
lines changed

31 files changed

+504
-38
lines changed

Cabal/doc/nix-local-build.rst

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ To open a GHCi shell with this package, use this command:
1717

1818
$ cabal new-repl
1919

20+
To run an executable defined in this package, use this command:
21+
22+
::
23+
24+
$ cabal new-run <executable name> [executable args]
25+
2026
Developing multiple packages
2127
----------------------------
2228

@@ -343,6 +349,26 @@ Currently, it is not supported to pass multiple targets to ``new-repl``
343349
(``new-repl`` will just successively open a separate GHCi session for
344350
each target.)
345351

352+
cabal new-run
353+
-------------
354+
355+
``cabal new-run [TARGET [ARGS]]`` runs the executable specified by the
356+
target, which can be a component, a package or can be left blank, as
357+
long as it can uniquely identify an executable within the project.
358+
359+
See `the new-build section <#cabal-new-build>`__ for the target syntax.
360+
361+
Except in the case of the empty target, the strings after it will be
362+
passed to the executable as arguments.
363+
364+
If one of the arguments starts with ``-`` it will be interpreted as
365+
a cabal flag, so if you need to pass flags to the executable you
366+
have to separate them with ``--``.
367+
368+
::
369+
370+
$ cabal new-run target -- -a -bcd --argument
371+
346372
cabal new-freeze
347373
----------------
348374

@@ -382,10 +408,6 @@ The following commands are not currently supported:
382408
Workaround: run the benchmark executable directly (see `Where are my
383409
build products <#where-are-my-build-products>`__?)
384410

385-
``cabal new-run`` (:issue:`3638`)
386-
Workaround: run the executable directly (see `Where are my build
387-
products <#where-are-my-build-products>`__?)
388-
389411
``cabal new-exec``
390412
Workaround: if you wanted to execute GHCi, consider using
391413
``cabal new-repl`` instead. Otherwise, use ``-v`` to find the list

cabal-install/Distribution/Client/CmdRun.hs

Lines changed: 199 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ module Distribution.Client.CmdRun (
1313
selectComponentTarget
1414
) where
1515

16+
import Prelude ()
17+
import Distribution.Client.Compat.Prelude
18+
1619
import Distribution.Client.ProjectOrchestration
1720
import Distribution.Client.CmdErrorMessages
1821

@@ -30,11 +33,34 @@ import Distribution.Text
3033
import Distribution.Verbosity
3134
( Verbosity, normal )
3235
import Distribution.Simple.Utils
33-
( wrapText, die', ordNub )
36+
( wrapText, die', ordNub, info )
37+
import Distribution.Types.PackageName
38+
( unPackageName )
39+
import Distribution.Client.ProjectPlanning
40+
( ElaboratedConfiguredPackage(..)
41+
, ElaboratedInstallPlan, binDirectoryFor )
42+
import Distribution.Client.InstallPlan
43+
( toList, foldPlanPackage )
44+
import Distribution.Client.ProjectPlanning.Types
45+
( ElaboratedPackageOrComponent(..)
46+
, ElaboratedComponent(compComponentName) )
47+
import Distribution.Types.Executable
48+
( Executable(exeName) )
49+
import Distribution.Types.UnqualComponentName
50+
( UnqualComponentName, unUnqualComponentName )
51+
import Distribution.Types.PackageDescription
52+
( PackageDescription(executables) )
53+
import Distribution.Simple.Program.Run
54+
( runProgramInvocation, simpleProgramInvocation )
55+
import Distribution.Types.PackageId
56+
( PackageIdentifier(..) )
3457

3558
import qualified Data.Map as Map
3659
import qualified Data.Set as Set
37-
import Control.Monad (when)
60+
import Data.Function
61+
( on )
62+
import System.FilePath
63+
( (</>) )
3864

3965

4066
runCommand :: CommandUI (ConfigFlags, ConfigExFlags, InstallFlags, HaddockFlags)
@@ -90,7 +116,8 @@ runAction (configFlags, configExFlags, installFlags, haddockFlags)
90116
baseCtx <- establishProjectBaseContext verbosity cliConfig
91117

92118
targetSelectors <- either (reportTargetSelectorProblems verbosity) return
93-
=<< readTargetSelectors (localPackages baseCtx) targetStrings
119+
=<< readTargetSelectors (localPackages baseCtx)
120+
(take 1 targetStrings) -- Drop the exe's args.
94121

95122
buildCtx <-
96123
runProjectPreBuildPhase verbosity baseCtx $ \elaboratedPlan -> do
@@ -128,12 +155,181 @@ runAction (configFlags, configExFlags, installFlags, haddockFlags)
128155

129156
buildOutcomes <- runProjectBuildPhase verbosity baseCtx buildCtx
130157
runProjectPostBuildPhase verbosity baseCtx buildCtx buildOutcomes
158+
159+
-- Get the selectors for the package and component.
160+
-- These are wrapped in Maybes, because the user
161+
-- might not specify them.
162+
(selectedPackage, selectedComponent) <-
163+
-- This should always match [x] anyway because
164+
-- we already check for a single target in TargetSelector.hs
165+
case selectorPackageAndComponent <$> targetSelectors
166+
of [x] -> return x
167+
[ ] -> die'
168+
verbosity
169+
"No targets given, but the run phase has been reached. This is a bug."
170+
_ -> die'
171+
verbosity
172+
"Multiple targets given, but the run phase has been reached. This is a bug."
173+
174+
let elaboratedPlan = elaboratedPlanOriginal buildCtx
175+
matchingElaboratedConfiguredPackages =
176+
extractMatchingElaboratedConfiguredPackages
177+
selectedPackage
178+
selectedComponent
179+
elaboratedPlan
180+
181+
-- The names to match. Used only for user feedback, as
182+
-- later on we extract the real ones (whereas these are
183+
-- wrapped in a Maybe) from the package itself.
184+
let selectedPackageNameToMatch = getPackageName <$> selectedPackage
185+
selectedComponentNameToMatch = getExeComponentName =<< selectedComponent
186+
187+
-- For each ElaboratedConfiguredPackage in the install plan, we
188+
-- identify candidate executables. We only keep them if both the
189+
-- package name and executable name match what the user asked for
190+
-- (a missing specification matches everything).
191+
--
192+
-- In the common case, we expect this to pick out a single
193+
-- ElaboratedConfiguredPackage that provides a single way of building
194+
-- an appropriately-named executable. In that case we prune our
195+
-- install plan to that UnitId and PackageTarget and continue.
196+
--
197+
-- However, multiple packages/components could provide that
198+
-- executable, or it's possible we don't find the executable anywhere
199+
-- in the build plan. I suppose in principle it's also possible that
200+
-- a single package provides an executable in two different ways,
201+
-- though that's probably a bug if. Anyway it's a good lint to report
202+
-- an error in all of these cases, even if some seem like they
203+
-- shouldn't happen.
204+
(pkg,exe) <- case matchingElaboratedConfiguredPackages of
205+
[] -> die' verbosity $ "Unknown executable"
206+
++ case selectedComponentNameToMatch
207+
of Just x -> " " ++ x
208+
Nothing -> ""
209+
++ case selectedPackageNameToMatch
210+
of Just x -> " in package " ++ x
211+
Nothing -> ""
212+
[(elabPkg,exe)] -> do
213+
info verbosity $ "Selecting " ++ display (elabUnitId elabPkg)
214+
++ case selectedComponentNameToMatch
215+
of Just x -> " to supply " ++ x
216+
Nothing -> ""
217+
return (elabPkg, unUnqualComponentName exe)
218+
elabPkgs -> die' verbosity
219+
$ "Multiple matching executables found"
220+
++ case selectedComponentNameToMatch
221+
of Just x -> " matching " ++ x
222+
Nothing -> ""
223+
++ ":\n"
224+
++ unlines (fmap (\(p,_) -> " - in package " ++ display (elabUnitId p)) elabPkgs)
225+
let exePath = binDirectoryFor (distDirLayout baseCtx)
226+
(elaboratedShared buildCtx)
227+
pkg
228+
exe
229+
</> exe
230+
let args = drop 1 targetStrings
231+
runProgramInvocation
232+
verbosity
233+
(simpleProgramInvocation exePath args)
131234
where
132235
verbosity = fromFlagOrDefault normal (configVerbosity configFlags)
133236
cliConfig = commandLineFlagsToProjectConfig
134237
globalFlags configFlags configExFlags
135238
installFlags haddockFlags
136239

240+
-- Package selection
241+
------
242+
243+
getPackageName :: PackageIdentifier -> String
244+
getPackageName (PackageIdentifier packageName _) =
245+
unPackageName packageName
246+
247+
getExeComponentName :: ComponentName -> Maybe String
248+
getExeComponentName (CExeName unqualComponentName) =
249+
Just $ unUnqualComponentName unqualComponentName
250+
getExeComponentName _ = Nothing
251+
252+
selectorPackageAndComponent :: TargetSelector PackageId
253+
-> (Maybe PackageId, Maybe ComponentName)
254+
selectorPackageAndComponent (TargetPackage _ pkg _) =
255+
(Just pkg, Nothing)
256+
selectorPackageAndComponent (TargetAllPackages _) =
257+
(Nothing, Nothing)
258+
selectorPackageAndComponent (TargetComponent pkg component _) =
259+
(Just pkg, Just component)
260+
261+
-- | Extract all 'ElaboratedConfiguredPackage's and executable names
262+
-- that match the user-provided component/package
263+
-- The component can be either:
264+
-- * specified by the user (both Just)
265+
-- * deduced from an user-specified package (the component is unspecified, Nothing)
266+
-- * deduced from the cwd (both the package and the component are unspecified)
267+
extractMatchingElaboratedConfiguredPackages
268+
:: Maybe PackageId -- ^ the package to match
269+
-> Maybe ComponentName -- ^ the component to match
270+
-> ElaboratedInstallPlan -- ^ a plan in with to search for matching exes
271+
-> [(ElaboratedConfiguredPackage, UnqualComponentName)] -- ^ the matching package and the exe name
272+
extractMatchingElaboratedConfiguredPackages
273+
pkgId component = nubBy equalPackageIdAndExe
274+
. catMaybes
275+
. fmap sequenceA' -- get the Maybe outside the tuple
276+
. fmap (\p -> (p, matchingExecutable p))
277+
. catMaybes
278+
. fmap (foldPlanPackage
279+
(const Nothing)
280+
(\x -> if match x
281+
then Just x
282+
else Nothing))
283+
. toList
284+
where
285+
-- We need to support ghc 7.6, so we don't have
286+
-- a sequenceA that works on tuples yet.
287+
-- Once we drop support for pre-ftp ghc
288+
-- it's safe to remove this.
289+
sequenceA' (a, Just b) = Just (a, b)
290+
sequenceA' _ = Nothing
291+
match :: ElaboratedConfiguredPackage -> Bool
292+
match p = matchPackage pkgId p && matchComponent component p
293+
matchingExecutable p = exactlyOne
294+
$ filter (\x -> Just x == componentString
295+
|| isNothing componentString)
296+
$ executablesOfPackage p
297+
componentString = componentNameString =<< component
298+
exactlyOne [x] = Just x
299+
exactlyOne _ = Nothing
300+
equalPackageIdAndExe (p,c) (p',c') = c==c' && ((==) `on` elabPkgSourceId) p p'
301+
302+
matchPackage :: Maybe PackageId
303+
-> ElaboratedConfiguredPackage
304+
-> Bool
305+
matchPackage pkgId pkg =
306+
pkgId == Just (elabPkgSourceId pkg)
307+
|| isNothing pkgId --if the package is unspecified (Nothing), all packages match
308+
309+
matchComponent :: Maybe ComponentName
310+
-> ElaboratedConfiguredPackage
311+
-> Bool
312+
matchComponent component pkg =
313+
componentString `elem` (Just <$> executablesOfPackage pkg)
314+
|| isNothing componentString --if the component is unspecified (Nothing), all components match
315+
where componentString = componentNameString =<< component
316+
317+
executablesOfPackage :: ElaboratedConfiguredPackage
318+
-> [UnqualComponentName]
319+
executablesOfPackage p =
320+
case exeFromComponent
321+
of Just exe -> [exe]
322+
Nothing -> exesFromPackage
323+
where
324+
exeFromComponent =
325+
case elabPkgOrComp p
326+
of ElabComponent comp -> case compComponentName comp
327+
of Just (CExeName exe) -> Just exe
328+
_ -> Nothing
329+
_ -> Nothing
330+
exesFromPackage = fmap exeName $ executables $ elabPkgDescription p
331+
332+
137333
-- | This defines what a 'TargetSelector' means for the @run@ command.
138334
-- It selects the 'AvailableTarget's that the 'TargetSelector' refers to,
139335
-- or otherwise classifies the problem.

0 commit comments

Comments
 (0)