-
Notifications
You must be signed in to change notification settings - Fork 691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new-run #4586
new-run #4586
Changes from 15 commits
4a1753e
e0734f3
6d2c200
e3a87d2
17958db
33d4362
041ffa3
41db836
a0727b2
aa682f9
b27fb01
65a25de
f9cb38d
6a969fc
1657537
22497b2
28dbf7f
6dfc20f
c0a50a3
9c5b475
2fd4c18
fa851f2
7f7cb93
8fd5225
b2e7523
81b5f4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,9 @@ module Distribution.Client.CmdRun ( | |
selectComponentTarget | ||
) where | ||
|
||
import Prelude () | ||
import Distribution.Client.Compat.Prelude | ||
|
||
import Distribution.Client.ProjectOrchestration | ||
import Distribution.Client.CmdErrorMessages | ||
|
||
|
@@ -30,11 +33,39 @@ import Distribution.Text | |
import Distribution.Verbosity | ||
( Verbosity, normal ) | ||
import Distribution.Simple.Utils | ||
( wrapText, die', ordNub ) | ||
( wrapText, die', ordNub, info ) | ||
import Distribution.Types.PackageName | ||
( unPackageName ) | ||
import Distribution.Client.ProjectPlanning | ||
( ElaboratedConfiguredPackage(..) | ||
, ElaboratedInstallPlan ) | ||
import Distribution.Client.InstallPlan | ||
( toList, foldPlanPackage ) | ||
import Distribution.Client.ProjectPlanning.Types | ||
( ElaboratedPackageOrComponent(..) | ||
, ElaboratedComponent(compComponentName) | ||
, BuildStyle(BuildInplaceOnly, BuildAndInstall) | ||
, ElaboratedSharedConfig, elabDistDirParams ) | ||
import Distribution.Types.Executable | ||
( Executable(exeName) ) | ||
import Distribution.Types.UnqualComponentName | ||
( UnqualComponentName, unUnqualComponentName ) | ||
import Distribution.Types.PackageDescription | ||
( PackageDescription(executables) ) | ||
import Distribution.Simple.Program.Run | ||
( runProgramInvocation, simpleProgramInvocation ) | ||
import Distribution.Types.PackageId | ||
( PackageIdentifier(..) ) | ||
import Distribution.Client.DistDirLayout | ||
( DistDirLayout, distBuildDirectory ) | ||
import qualified Distribution.Simple.InstallDirs as InstallDirs | ||
|
||
import qualified Data.Map as Map | ||
import qualified Data.Set as Set | ||
import Control.Monad (when) | ||
import Data.Function | ||
( on ) | ||
import System.FilePath | ||
( (</>) ) | ||
|
||
|
||
runCommand :: CommandUI (ConfigFlags, ConfigExFlags, InstallFlags, HaddockFlags) | ||
|
@@ -90,7 +121,8 @@ runAction (configFlags, configExFlags, installFlags, haddockFlags) | |
baseCtx <- establishProjectBaseContext verbosity cliConfig | ||
|
||
targetSelectors <- either (reportTargetSelectorProblems verbosity) return | ||
=<< readTargetSelectors (localPackages baseCtx) targetStrings | ||
=<< readTargetSelectors (localPackages baseCtx) | ||
(take 1 targetStrings) -- we drop the exe's args | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Urk. Wouldn't it be better to split targetStrings first before doing anything with it? See (A). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See (A). |
||
|
||
buildCtx <- | ||
runProjectPreBuildPhase verbosity baseCtx $ \elaboratedPlan -> do | ||
|
@@ -128,12 +160,209 @@ runAction (configFlags, configExFlags, installFlags, haddockFlags) | |
|
||
buildOutcomes <- runProjectBuildPhase verbosity baseCtx buildCtx | ||
runProjectPostBuildPhase verbosity baseCtx buildCtx buildOutcomes | ||
|
||
-- We get the selectors for the package and component. | ||
-- These are wrapped in Maybes, because the user | ||
-- might not specify them | ||
(selectedPackage, selectedComponent) <- | ||
-- this should always match [x] anyway because | ||
-- we already check for a single target in TargetSelector.hs | ||
case selectorPackageAndComponent <$> targetSelectors | ||
of [x] -> return x | ||
[ ] -> die' | ||
verbosity | ||
"No targets given" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't this error message occur when I type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (B2) No arguments means "treat the package in the cwd/project as a target, if there is one" (look at the comment above extractMatchingElaboratedConfiguredPackages). But this is also handled before, so same as (B1). |
||
_ -> die' | ||
verbosity | ||
"Multiple targets given" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This cannot happen, by virtue of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (B1) Not by virtue of the take 1. For example if we specify a package with 2 exes in the targetStrings there will be two targets. But this is handled by lines 138-149 already, so you are correct. Should iI use an assert? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, did i just duplicate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think "multiple targets" is a bit misleading. It's more like a single, ambiguous target. Perhaps the error could be improved by mentioning longer target strings the user might want to try to disambiguate between the possibilities, e.g. "Ambiguous target my-package; could match my-package:my-exe1 or my-package:my-exe2" or "Ambiguous target my-exe; could match my-package1:my-exe or my-package2:my-exe". (Or am I misunderstanding when we would hit this branch?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
let elaboratedPlan = elaboratedPlanOriginal buildCtx | ||
matchingElaboratedConfiguredPackages = | ||
extractMatchingElaboratedConfiguredPackages | ||
selectedPackage | ||
selectedComponent | ||
elaboratedPlan | ||
|
||
-- the names to match. used only for user feedback, as | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use proper punctuation in comments (i.e. start sentences with capital letters, end them with full stops, and so on). |
||
-- later on we extract the real ones (whereas these are | ||
-- wrapped in a Maybe) from the package itself | ||
let selectedPackageNameToMatch = getPackageName <$> selectedPackage | ||
selectedComponentNameToMatch = getExeComponentName =<< selectedComponent | ||
|
||
-- For each ElaboratedConfiguredPackage in the install plan, we | ||
-- identify candidate executables. We only keep them if both the | ||
-- package name and executable name match what the user asked for | ||
-- (a missing specification matches everything). | ||
-- | ||
-- In the common case, we expect this to pick out a single | ||
-- ElaboratedConfiguredPackage that provides a single way of building | ||
-- an appropriately-named executable. In that case we prune our | ||
-- install plan to that UnitId and PackageTarget and continue. | ||
-- | ||
-- However, multiple packages/components could provide that | ||
-- executable, or it's possible we don't find the executable anywhere | ||
-- in the build plan. I suppose in principle it's also possible that | ||
-- a single package provides an executable in two different ways, | ||
-- though that's probably a bug if. Anyway it's a good lint to report | ||
-- an error in all of these cases, even if some seem like they | ||
-- shouldn't happen. | ||
(pkg,exe) <- case matchingElaboratedConfiguredPackages of | ||
[] -> die' verbosity $ "Unknown executable" | ||
++ case selectedComponentNameToMatch | ||
of Just x -> " " ++ x | ||
Nothing -> "" | ||
++ case selectedPackageNameToMatch | ||
of Just x -> " in package " ++ x | ||
Nothing -> "" | ||
[(elabPkg,exe)] -> do | ||
info verbosity $ "Selecting " ++ display (elabUnitId elabPkg) | ||
++ case selectedComponentNameToMatch | ||
of Just x -> " to supply " ++ x | ||
Nothing -> "" | ||
return (elabPkg, unUnqualComponentName exe) | ||
elabPkgs -> die' verbosity | ||
$ "Multiple matching executables found" | ||
++ case selectedComponentNameToMatch | ||
of Just x -> " matching " ++ x | ||
Nothing -> "" | ||
++ ":\n" | ||
++ unlines (fmap (\(p,_) -> " - in package " ++ display (elabUnitId p)) elabPkgs) | ||
let exePath = binDirectoryFor (distDirLayout baseCtx) | ||
(elaboratedShared buildCtx) | ||
pkg | ||
exe | ||
</> exe | ||
let args = drop 1 targetStrings | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (A) Then it wouldn't be necessary to subsequently drop it here! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did this because (see B1 and B2) we don't know anything about the targets at the beginning of the function, and an empty targetStrings could have a meaning. If i wrote a match for (command:args) and [] then I'd have to duplicate most of the code or put everything in an helper function. the take/drop approach handles well the absence of a target component. But now that i think of it, what if we do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea. Cabal's flags are filtered from the targetStrings already, so it should simply be a matter of checking the first targetString There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No it isn't. The first But if we document it appropriately we could use two of them, the first separating flags and targets, the second targets and targets' args (actually its only use will be as a "placeholder" for the target when args are also needed, like @dmwit says) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. btw, #3638 may be the origin of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apparently even Fixing this requires some modifications to the args parsing, but I don't think this is needed, as this was also the behaviour of old-run (except #4600). edit: also There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep in mind: modeling our behavior after old-style |
||
runProgramInvocation | ||
verbosity | ||
(simpleProgramInvocation exePath args) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you want runProgramInvocation here; it doesn't propagate the exit code. Use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I checked and it does propagate it, as it uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have a test case that checks that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now we have one |
||
where | ||
verbosity = fromFlagOrDefault normal (configVerbosity configFlags) | ||
cliConfig = commandLineFlagsToProjectConfig | ||
globalFlags configFlags configExFlags | ||
installFlags haddockFlags | ||
|
||
-- Package selection | ||
------ | ||
|
||
getPackageName :: PackageIdentifier -> String | ||
getPackageName (PackageIdentifier packageName _) = | ||
unPackageName packageName | ||
|
||
getExeComponentName :: ComponentName -> Maybe String | ||
getExeComponentName (CExeName unqualComponentName) = | ||
Just $ unUnqualComponentName unqualComponentName | ||
getExeComponentName _ = Nothing | ||
|
||
selectorPackageAndComponent :: TargetSelector PackageId | ||
-> (Maybe PackageId, Maybe ComponentName) | ||
selectorPackageAndComponent (TargetPackage _ pkg _) = | ||
(Just pkg, Nothing) | ||
selectorPackageAndComponent (TargetAllPackages _) = | ||
(Nothing, Nothing) | ||
selectorPackageAndComponent (TargetComponent pkg component _) = | ||
(Just pkg, Just component) | ||
|
||
-- | Extract all 'ElaboratedConfiguredPackage's and executable names | ||
-- that match the user-provided component/package | ||
-- The component can be either: | ||
-- * specified by the user (both Just) | ||
-- * deduced from an user-specified package (the component is unspecified, Nothing) | ||
-- * deduced from the cwd (both the package and the component are unspecified) | ||
extractMatchingElaboratedConfiguredPackages | ||
:: Maybe PackageId -- ^ the package to match | ||
-> Maybe ComponentName -- ^ the component to match | ||
-> ElaboratedInstallPlan -- ^ a plan in with to search for matching exes | ||
-> [(ElaboratedConfiguredPackage, UnqualComponentName)] -- ^ the matching package and the exe name | ||
extractMatchingElaboratedConfiguredPackages | ||
pkgId component = nubBy equalPackageIdAndExe | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is a nub necessary here? If the same package and component show up multiple times, isn't that a problem? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the there are multiple exes, then for each exe there will be an For each I can't dedup before that point because i need both the package and the component name to do so (what if two packages have the seme exe name?). Maybe I should add a comment about this. Or maybe it's a bug? Or should I add another field to |
||
. catMaybes | ||
. fmap sequenceA' -- get the Maybe outside the tuple | ||
. fmap (\p -> (p, matchingExecutable p)) | ||
. catMaybes | ||
. fmap (foldPlanPackage | ||
(const Nothing) | ||
(\x -> if match x | ||
then Just x | ||
else Nothing)) | ||
. toList | ||
where | ||
-- We need to support ghc 7.6, so we don't have | ||
-- a sequenceA that works on tuples yet. | ||
-- Once we drop support for pre-ftp ghc | ||
-- it's safe to remove this. | ||
sequenceA' (a, Just b) = Just (a, b) | ||
sequenceA' _ = Nothing | ||
match :: ElaboratedConfiguredPackage -> Bool | ||
match p = matchPackage pkgId p && matchComponent component p | ||
matchingExecutable p = atMostOne | ||
$ filter (\x -> Just x == componentString | ||
|| isNothing componentString) | ||
$ executablesOfPackage p | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't have to change this, but my preference here would have been to case on p here for the package versus component case, rather than put everything through the "get the list of components it defines, and now check if anything in that list matches". My primary reasoning here is that the component case is more reflective of what the "good" code looks like, but the way things are wired up now it makes it seem like the package case (where multiple components may exist) is more important. |
||
componentString = componentNameString =<< component | ||
atMostOne [x] = Just x | ||
atMostOne _ = Nothing | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. exactlyOne? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or safeHead, or headMaybe... yeah, that probably wasn't the best name :-P |
||
equalPackageIdAndExe (p,c) (p',c') = c==c' && ((==) `on` elabPkgSourceId) p p' | ||
|
||
matchPackage :: Maybe PackageId | ||
-> ElaboratedConfiguredPackage | ||
-> Bool | ||
matchPackage pkgId pkg = | ||
pkgId == Just (elabPkgSourceId pkg) | ||
|| isNothing pkgId --if the package is unspecified (Nothing), all packages match | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Slight bikeshed:
since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks ok, but I'd prefer to flip the arguments (so the "thing to search/filter for" is first as usual). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm also tempted to change Maybe with something like |
||
|
||
matchComponent :: Maybe ComponentName | ||
-> ElaboratedConfiguredPackage | ||
-> Bool | ||
matchComponent component pkg = | ||
componentString `elem` (Just <$> executablesOfPackage pkg) | ||
|| isNothing componentString --if the component is unspecified (Nothing), all components match | ||
where componentString = componentNameString =<< component | ||
|
||
executablesOfPackage :: ElaboratedConfiguredPackage | ||
-> [UnqualComponentName] | ||
executablesOfPackage p = | ||
case exeFromComponent | ||
of Just exe -> [exe] | ||
Nothing -> exesFromPackage | ||
where | ||
exeFromComponent = | ||
case elabPkgOrComp p | ||
of ElabComponent comp -> case compComponentName comp | ||
of Just (CExeName exe) -> Just exe | ||
_ -> Nothing | ||
_ -> Nothing | ||
exesFromPackage = fmap exeName $ executables $ elabPkgDescription p | ||
|
||
-- Path construction | ||
------ | ||
|
||
-- | The path to the @build@ directory for an inplace build. | ||
inplaceBinRoot | ||
:: DistDirLayout | ||
-> ElaboratedSharedConfig | ||
-> ElaboratedConfiguredPackage | ||
-> FilePath | ||
inplaceBinRoot layout config package | ||
= distBuildDirectory layout (elabDistDirParams config package) | ||
</> "build" | ||
|
||
-- | The path to the directory that contains a specific executable. | ||
binDirectoryFor | ||
:: DistDirLayout | ||
-> ElaboratedSharedConfig | ||
-> ElaboratedConfiguredPackage | ||
-> FilePath | ||
-> FilePath | ||
binDirectoryFor layout config package exe = case elabBuildStyle package of | ||
BuildAndInstall -> installedBinDirectory package | ||
BuildInplaceOnly -> inplaceBinRoot layout config package </> exe | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic is duplicated with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will merge. |
||
|
||
-- package has been built and installed. | ||
installedBinDirectory :: ElaboratedConfiguredPackage -> FilePath | ||
installedBinDirectory = InstallDirs.bindir . elabInstallDirs | ||
|
||
|
||
-- | This defines what a 'TargetSelector' means for the @run@ command. | ||
-- It selects the 'AvailableTarget's that the 'TargetSelector' refers to, | ||
-- or otherwise classifies the problem. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# cabal new-run | ||
Resolving dependencies... | ||
In order, the following will be built: | ||
- T4477-1.0 (exe:foo) (first run) | ||
Configuring executable 'foo' for T4477-1.0.. | ||
Preprocessing executable 'foo' for T4477-1.0.. | ||
Building executable 'foo' for T4477-1.0.. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe not for this PR, but we should think carefully about whether or new-run should output this sort of information. In favor: it's good to be able to see when your executable is being built. Against: you won't be able to use new-run to run executables that you want to run as part of a pipeline, since there will be extra Cabal build progress goo in your stderr. Perhaps this is related to the desire for a mode, "please run this as quickly as possible, don't rebuild at all!" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The question of whether new-run should output its own information is a good one. My stance on "build or not" is that new-run should always rebuild the appropriate files, and new-exec should never rebuild anything (but should ensure that all the project's executables would be on the PATH if they were built). This makes the divide nice and clean, and supports both needs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We also have -v0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
import Test.Cabal.Prelude | ||
main = cabalTest $ do | ||
expectBroken 4477 $ do | ||
cabal' "new-run" ["foo"] >>= assertOutputContains "Hello World" | ||
cabal' "new-run" ["foo"] >>= assertOutputContains "Hello World" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is too bad there is no singular version of readTargetSelectors, since it seems like that is what you want.