Skip to content
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

Add a cabal target command #9744

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

philderbeast
Copy link
Collaborator

@philderbeast philderbeast commented Feb 26, 2024

Adds a cabal target command for showing targets that can be supplied to other commands like the cabal build command.

The initial motivation would have been #8953 but this is not a fix for that issue but it could be if the results are filtered. For example lib dependencies for all:tests are not filtered yet.

Related issues are #8683, #9732, #1382 and #4070.

I took the cabal build command and cut it short where it made a call to printPlan. The cabal target command calls printPlanTargetForms instead that is a modifiation of printPlan. Is there another way to get the targets (an easier way or a better way)?

Here's the help;

$ cabal --help
...
[running and testing]
  target                 List target forms within the project.
  list-bin               List the path to a single executable.
...
[new-style projects (forwards-compatible aliases)]
  v2-target              List target forms within the project.
  v2-build               Compile targets within the project.
...

Note

I've changed the above cabal --help to the following.

$ cabal --help
...
 [project building and installing]
  target                 Target disclosure.
  build                  Compile targets within the project.
...
 [new-style projects (forwards-compatible aliases)]
  v2-target              Target disclosure.
  v2-build               Compile targets within the project.
...

The command specific help, a lot of which is taken straight from the user guide on target forms;

$ cabal target --help
List target forms within the project.

Usage: cabal target [TARGETS]

List targets within a build plan. If no [TARGETS] are given 'all' will be used
for selecting a build plan.

The given target can be;
- a package target (e.g. [pkg:]package)
- a component target (e.g. [package:][ctype:]component)
- all packages (e.g. all)
- components of a particular type (e.g. package:ctypes or all:ctypes)
- a module target: (e.g. [package:][ctype:]module)
- a filepath target: (e.g. [package:][ctype:]filepath)
- a script target: (e.g. path/to/script)

The ctypes can be one of: libs or libraries, exes or executables, tests,
benches or benchmarks, and flibs or foreign-libraries.

Flags for target:
 -h, --help                     Show this help text

Examples:
  cabal target all
    List all targets of the package in the current directory or all packages in the project
  cabal target pkgname
    List targets of the package named pkgname in the project
  cabal target ./pkgfoo
    List targets of the package in the ./pkgfoo directory
  cabal target cname
    List targets of the component named cname in the project

Note

I've changed the above cabal target --help to the following.

$ cabal -- target --help
Target disclosure.

Usage: cabal target [TARGETS]

Reveal the targets of build plan. If no [TARGETS] are given 'all' will be used
for selecting a build plan.

A [TARGETS] item can be one of these target forms;
- a package target (e.g. [pkg:]package)
- a component target (e.g. [package:][ctype:]component)
- all packages (e.g. all)
- components of a particular type (e.g. package:ctypes or all:ctypes)
- a module target: (e.g. [package:][ctype:]module)
- a filepath target: (e.g. [package:][ctype:]filepath)
- a script target: (e.g. path/to/script)

The ctypes can be one of: libs or libraries, exes or executables, tests,
benches or benchmarks, and flibs or foreign-libraries.

Flags for target:
 -h, --help                     Show this help text

Examples:
  cabal target all
    Targets of the package in the current directory or all packages in the project
  cabal target pkgname
    Targets of the package named pkgname in the project
  cabal target ./pkgfoo
    Targets of the package in the ./pkgfoo directory
  cabal target cname
    Targets of the component named cname in the project

The command in action on the cabal project;

$ cabal target all
Resolving dependencies...
 - Cabal-QuickCheck:lib
 - Cabal-described:lib
 - Cabal-syntax:lib
 - Cabal-tests:lib
 - Cabal-tests:test:check-tests
 - Cabal-tests:test:custom-setup-tests
 - Cabal-tests:test:hackage-tests
 - Cabal-tests:test:no-thunks-test
 - Cabal-tests:test:parser-tests
 - Cabal-tests:test:rpmvercmp
 - Cabal-tests:test:unit-tests
 - Cabal-tree-diff:lib
 - Cabal:lib
 - cabal-benchmarks:test:cabal-benchmarks
 - cabal-install-solver:lib
 - cabal-install-solver:test:unit-tests
 - cabal-install:exe:cabal
 - cabal-install:lib
 - cabal-install:test:integration-tests2
 - cabal-install:test:long-tests
 - cabal-install:test:mem-use-tests
 - cabal-install:test:unit-tests
 - cabal-testsuite
 - hackage-security:lib
 - solver-benchmarks:exe:hackage-benchmark
 - solver-benchmarks:lib
 - solver-benchmarks:test:unit-tests

Notice that I'm not stripping out local dependencies that must also be built by the plan;

$ cabal run cabal -- target all:tests
Warning: this is a debug build of cabal-install with assertions enabled.
Resolving dependencies...
Warning: this is a debug build of cabal-install with assertions enabled.
Resolving dependencies...
 - Cabal-QuickCheck:lib
 - Cabal-described:lib
 - Cabal-syntax:lib
 - Cabal-tests:lib
 - Cabal-tests:test:check-tests
 - Cabal-tests:test:custom-setup-tests
 - Cabal-tests:test:hackage-tests
 - Cabal-tests:test:no-thunks-test
 - Cabal-tests:test:parser-tests
 - Cabal-tests:test:rpmvercmp
 - Cabal-tests:test:unit-tests
 - Cabal-tree-diff:lib
 - Cabal:lib
 - cabal-benchmarks:test:cabal-benchmarks
 - cabal-install-solver:lib
 - cabal-install-solver:test:unit-tests
 - cabal-install:lib
 - cabal-install:test:integration-tests2
 - cabal-install:test:long-tests
 - cabal-install:test:mem-use-tests
 - cabal-install:test:unit-tests
 - hackage-security:lib
 - solver-benchmarks:lib
 - solver-benchmarks:test:unit-tests

@fendor
Copy link
Collaborator

fendor commented Feb 26, 2024

Related PR: #7500

@philderbeast
Copy link
Collaborator Author

Thanks for the link @fendor. I can see you've put a lot of effort into a larger scoped change.

@alt-romes
Copy link
Collaborator

alt-romes commented Feb 27, 2024

This looks plausible to me. We definitely need a test and changelog entry -- though I understand this only makes sense to do if the feature is generally agreed upon, which I can't guarantee by myself. I'll bring it up in the dev meeting on Thursday.

@philderbeast
Copy link
Collaborator Author

philderbeast commented Feb 27, 2024

Because this uses --dry-run effectively, if dependencies haven't been built (I got there by changing branches, cabal clean won't invalidate already built dependencies) then these too will show up as targets.

Note

I'm suprised to see Cabal:lib there twice.

$ cabal target all
Resolving dependencies...
 - Cabal-QuickCheck:lib
 - Cabal-described:lib
 - Cabal-syntax:lib
 - Cabal-tests:lib
 - Cabal-tests:test:check-tests
 - Cabal-tests:test:custom-setup-tests
 - Cabal-tests:test:hackage-tests
 - Cabal-tests:test:no-thunks-test
 - Cabal-tests:test:parser-tests
 - Cabal-tests:test:rpmvercmp
 - Cabal-tests:test:unit-tests
 - Cabal-tree-diff:lib
 - Cabal:lib
 - Cabal:lib
 - Diff:lib
 - OneTuple:lib
 - QuickCheck:lib
 - StateVar:lib
 - aeson:lib
 - ansi-terminal-types:lib
 - ansi-terminal:lib
 - ansi-wl-pprint:lib
 - assoc:lib
 - attoparsec:lib
 - attoparsec:lib:attoparsec-internal
 - base-compat:lib
 - base-orphans:lib
 - bifunctors:lib
 - bitvec:lib
 - bytestring-builder:lib
 - cabal-benchmarks:test:cabal-benchmarks
 - cabal-install-solver:lib
 - cabal-install-solver:test:unit-tests
 - cabal-install:exe:cabal
 - cabal-install:lib
 - cabal-install:test:integration-tests2
 - cabal-install:test:long-tests
 - cabal-install:test:mem-use-tests
 - cabal-install:test:unit-tests
 - cabal-testsuite
 - call-stack:lib
 - charset:lib
 - clock:lib
 - colour:lib
 - comonad:lib
 - contravariant:lib
 - data-default-class(lib:data-default-class)
 - data-fix:lib
 - dense-linear-algebra:lib
 - distributive:lib
 - dlist:lib
 - generically:lib
 - hackage-security:lib
 - happy:exe:happy
 - haskell-lexer:lib
 - indexed-traversable-instances:lib
 - indexed-traversable:lib
 - integer-conversion:lib
 - integer-logarithms:lib
 - math-functions:lib
 - mtl-compat:lib
 - mwc-random:lib
 - network-wait:lib
 - nothunks:lib
 - optparse-applicative:lib
 - parallel:lib
 - parsers:lib
 - pretty-show:lib
 - prettyprinter-ansi-terminal:lib
 - prettyprinter-compat-ansi-wl-pprint:lib
 - prettyprinter:lib
 - primitive:lib
 - regex-tdfa:lib
 - rere:lib
 - retry:lib
 - scientific:lib
 - semialign:lib
 - semigroupoids:lib
 - solver-benchmarks:exe:hackage-benchmark
 - solver-benchmarks:lib
 - solver-benchmarks:test:unit-tests
 - statistics:lib
 - strict:lib
 - tagged:lib
 - tasty-bench:lib
 - tasty-expected-failure:lib
 - tasty-golden:lib
 - tasty-hunit:lib
 - tasty-quickcheck:lib
 - tasty:lib
 - temporary:lib
 - text-iso8601:lib
 - text-short:lib
 - th-abstraction:lib
 - these:lib
 - time-compat:lib
 - transformers-compat:lib
 - tree-diff:lib
 - typed-process:lib
 - unbounded-delays:lib
 - unliftio-core:lib
 - unordered-containers:lib
 - uuid-types:lib
 - vector-algorithms:lib
 - vector-binary-instances:lib
 - vector-stream:lib
 - vector-th-unbox:lib
 - vector:lib
 - wherefrom-compat:lib
 - witherable:lib

@philderbeast
Copy link
Collaborator Author

In investigating further, trying to build only the dependencies, I hit an error I've never seen before;

$ git rev-parse HEAD
6d9267d78b35c5075171309170825d42e4fd16e6

$ cabal build all --only-dependencies
Warning: this is a debug build of cabal-install with assertions enabled.
Error: [Cabal-7072]
Cannot select only the dependencies (as requested by the '--only-dependencies' flag),
the package Cabal-syntax-3.11.0.0 is required by a dependency of one of the other targets.

@andreabedini
Copy link
Collaborator

Maybe list-target(s)? It seems to follow naturally from the help message:

target                 List target forms within the project.
list-bin               List the path to a single executable.

Let me emphasise that those are only the targets within the project (as the help already points out) and that different commands can interpret their arguments more liberally (e.g. any package-id is a valid target for cabal install).

@andreabedini
Copy link
Collaborator

@philderbeast Ill give it a proper review ASAP

@philderbeast
Copy link
Collaborator Author

philderbeast commented Feb 28, 2024

Maybe list-target(s)? It seems to follow naturally from the help message:

target                 List target forms within the project.
list-bin               List the path to a single executable.

Let me emphasise that those are only the targets within the project (as the help already points out) and that different commands can interpret their arguments more liberally (e.g. any package-id is a valid target for cabal install).

I dislike the list-bin command name and wish it was named something else, like bin-path. I went with target instead of targets because none of the other command names are plural. They're all verbs aren't they?

@michaelpj
Copy link
Collaborator

What about cabal target list? There is some consensus that subcommands with the form <cmd> <noun> <verb> are the way to go. But then perhaps "target" is not a noun that we want to attach many verbs to 🤷

@andreasabel
Copy link
Member

Can we have a cabal list command that allows subcommands that all display some information about what cabal thinks about the world?

  • cabal list targets This PR.
  • cabal list bins Alt. name of cabal list-bin.
  • cabal list paths Alt. name of cabal path.
  • etc.

@philderbeast
Copy link
Collaborator Author

philderbeast commented Feb 29, 2024

What about cabal target list?

Yes @michaelpj a subcommand instead is a possibility. As implemented this is acting as cabal build --dry-run but printing something different at the end, printing the targets rather than what would be built. So that would be something like cabal build --print-targets.

@Mikolaj
Copy link
Member

Mikolaj commented Mar 5, 2024

I like cabal build --print-targets, because then we don't have to think whether it's going to be used a lot or only in very special circumstances (a top-level command would probably require establishing it's going to be used often enough). Maybe let's make it even more non-intrusive (though harder to use) and let cabal build --print-targets really build and then print the built targets (or separately the targets that are built and the targets that could be built) and cabal build --dry-run --print-targets show only all the possible targets? That's a more radical interpretation of your "but printing something different at the end" (or maybe it's "and printing some extra info at the end"). Makes sense? Then it's even more fine if the result with and without --dry-run or with and without stuff already built differs (cabal commands are stateful).

@philderbeast
Copy link
Collaborator Author

I favour --print-targets too. Saw that this is a new make option;

As of Jan 8th, 2024, Make has a --print-targets option that should do this properly without hacky regexes. The current version is Make 4.4.1 so the next release after that will have this feature.
SOURCE: How do you get the list of targets in a makefile?

@michaelpj
Copy link
Collaborator

-1 for attaching this to cabal build

  • Sometimes you don't want to build! Now you need cabal build --dont-build --print-targets. Yes, we already have --dry-run, but that's supposed to tell you what it was going to build, so how are we going to glue the output together? Awkward. It's just generally difficult making a command do double duty.
  • Most importantly, the targets are not uniquely associated with building. You can also use them in repl and many other commands. So do we add cabal repl --print-targets too? It's just not a build thing. In contrast, --dry-run tells you what would have been built, so really does make sense to belong to cabal build.

Make is different because a) it doesn't have sub-commands and b) the only operation on targets is building them.

Food for thought: https://doc.rust-lang.org/cargo/commands/cargo-metadata.html

@philderbeast
Copy link
Collaborator Author

My main motivation for this change, no matter which command or subcommand or option we put it under, is to help interactive use of cabal itself and not for producing a machine readable format for IDE tooling like the cabal status enchancement of #7500. Lots of commands take targets. This change shows me those targets.

@fendor
Copy link
Collaborator

fendor commented Mar 10, 2024

I understand and support this motivation. cabal needs to be easier to hack around with. The cabal status command was also intended to improve the cabal UX, allowing exactly this use-case, but I deferred the decision of what the UX should look like, in favour of a well-defined machine-readable output.

I think we should avoid too many top-level commands, as I fear trouble down the line with UX and discoverability.

While cabal list is already taken as a command, perhaps cabal project targets could be nice?

@philderbeast
Copy link
Collaborator Author

perhaps cabal project targets could be nice?

We have targets when there is no actual project file, when there is just a .cabal file.

@philderbeast
Copy link
Collaborator Author

  • Most importantly, the targets are not uniquely associated with building.

So a top-level targets command makes sense, not subordinate to some other command.

@philderbeast
Copy link
Collaborator Author

philderbeast commented Mar 17, 2024

What about cabal target list?

What else would we do with targets rather than list them that is not already covered by other commands taking targets? Perhaps we should pluralize it as cabal targets. We're asking them to identify themselves with this command.

@Mikolaj
Copy link
Member

Mikolaj commented May 1, 2024

Let's keep discussing this. Does anybody remember the conclusions of the discussion of the command names (related to cabal path, but also more general) we had at the open fortnightly cabal devs meeting recently?

@philderbeast
Copy link
Collaborator Author

Can we have a cabal list command that allows subcommands that all display some information about what cabal thinks about the world?

  • cabal list targets This PR.
  • cabal list bins Alt. name of cabal list-bin.
  • cabal list paths Alt. name of cabal path.
  • etc.

@andreasabel are you suggesting we keep cabal target but also add cabal list targets?

    $ cabal list targets --help
    Target disclosure and synonym of command 'cabal target'.

-   Usage: cabal target [TARGETS]
+   Usage: cabal list targets [TARGETS]

    Reveal the targets of build plan. If no [TARGETS] are given 'all' will be used
    for selecting a build plan.

    A [TARGETS] item can be one of these target forms;
    - a package target (e.g. [pkg:]package)
    - a component target (e.g. [package:][ctype:]component)
    - all packages (e.g. all)
    - components of a particular type (e.g. package:ctypes or all:ctypes)
    - a module target: (e.g. [package:][ctype:]module)
    - a filepath target: (e.g. [package:][ctype:]filepath)
    - a script target: (e.g. path/to/script)

    The ctypes can be one of: libs or libraries, exes or executables, tests,
    benches or benchmarks, and flibs or foreign-libraries.

    Flags for target:
    -h, --help                     Show this help text

    Examples:
-   cabal target all
+   cabal list targets all
        Targets of the package in the current directory or all packages in the project
-   cabal target pkgname
+   cabal list targets pkgname
        Targets of the package named pkgname in the project
-   cabal target ./pkgfoo
+   cabal list targets ./pkgfoo
        Targets of the package in the ./pkgfoo directory
-   cabal target cname
+   cabal list targets cname
        Targets of the component named cname in the project

@philderbeast
Copy link
Collaborator Author

Because this uses --dry-run effectively, if dependencies haven't been built (I got there by changing branches, cabal clean won't invalidate already built dependencies) then these too will show up as targets.

I'm not sure about this. Even with haskell/containers@c651094 having been built, I'm seeing targets from dependencies shown in the output;

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 9.8.2

$ cabal build all --enable-tests --enable-benchmarks
Up to date

$ cabal target
 - ChasingBottoms:lib
 - QuickCheck:lib
 - binary:lib
 - containers-tests:bench:graph-benchmarks
 - containers-tests:bench:intmap-benchmarks
 - containers-tests:bench:intset-benchmarks
 - containers-tests:bench:lookupge-intmap
 - containers-tests:bench:lookupge-map
 - containers-tests:bench:map-benchmarks
 - containers-tests:bench:sequence-benchmarks
 - containers-tests:bench:set-benchmarks
 - containers-tests:bench:set-operations-intmap
 - containers-tests:bench:set-operations-intset
 - containers-tests:bench:set-operations-map
 - containers-tests:bench:set-operations-set
 - containers-tests:bench:tree-benchmarks
 - containers-tests:lib
 - containers-tests:test:bitqueue-properties
 - containers-tests:test:graph-properties
 - containers-tests:test:intmap-lazy-properties
 - containers-tests:test:intmap-strict-properties
 - containers-tests:test:intmap-strictness-properties
 - containers-tests:test:intset-properties
 - containers-tests:test:intset-strictness-properties
 - containers-tests:test:listutils-properties
 - containers-tests:test:map-lazy-properties
 - containers-tests:test:map-strict-properties
 - containers-tests:test:map-strictness-properties
 - containers-tests:test:seq-properties
 - containers-tests:test:set-properties
 - containers-tests:test:tree-properties
 - containers:lib
 - ghc-heap:lib
 - nothunks:lib
 - optparse-applicative:lib
 - prettyprinter-ansi-terminal:lib
 - prettyprinter:lib
 - tasty-bench:lib
 - tasty-hunit:lib
 - tasty-quickcheck:lib
 - tasty:lib
 - text:lib

Copy link
Collaborator

@andreabedini andreabedini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to obtain list the targets you probably do not need all most of targetAction.

withContextAndSelectors is what powers cabal run myscript.hs unless you want to support cabal target myscript.hs you won't need it. If you do not use it, cabal target will only work in a project context. This is what I would expect.

runProjectPreBuildPhase rebuilds the project plan and then uses the function you pass to trim it down to only the required targets. Again, if you just want to list the available targets, you don't need to trim anything. Also it marks the up-to-date targets as 'installed', which means you won't get them from InstallPlan.executionOrder.

Assuming I understand what you want to do, I think you need to:

  • use establishProjectBaseContext to read the project configuration
  • use rebuildInstallPlan to get the full plan
  • use readTargetSelectors to parse the target selector strings on the command line into a list of TargetSelector
  • use resolveTargets to resolve those target selectors against the plan

This gives you a TargetsMap associating each unit in the plan with a list of components and the matched selectors.

type TargetsMap = Map UnitId [(ComponentTarget, NonEmpty TargetSelector)]

I guess this is want you might want to display. You might need to lookup the unit ids in the plan again though, which is a bit annoying.

I hope I didn't misunderstood what you are trying to do.

:: Verbosity
-> ProjectBuildContext
-> IO ()
printPlanTargetForms
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function shows every single single component in elaboratedPlanToExecute. This does not correspond to the list of available targets.

E.g.

✦ ~/code/cabal branchless/09946f1d1fa1217f4480d9ac919e2b264b5a3ecb*
λ $(cabal list-bin cabal) target
...
 - uuid-types:lib
 - vector-algorithms:lib
 - vector-binary-instances:lib
 - vector-stream:lib
 - vector-th-unbox:lib
 - vector:lib
 - wherefrom-compat:lib
 - witherable:lib
 - zinza:lib
 - zlib:lib

✦ ~/code/cabal branchless/09946f1d1fa1217f4480d9ac919e2b264b5a3ecb* 10s
λ $(cabal list-bin cabal) build zlib:lib
Warning: this is a debug build of cabal-install with assertions enabled.
Error: [Cabal-7130]
Internal error in target matching: could not make an unambiguous fully qualified target selector for 'zlib:lib'.
We made the target 'zlib:lib' (unknown-component) that was expected to be unambiguous but matches the following targets:
'zlib:lib', matching:
  - zlib:lib (unknown-component)
  - :pkg:zlib:lib:zlib:file:lib (unknown-file)

Note: Cabal expects to be able to make a single fully qualified name for a target or provide a more specific error. Our failure to do so is a bug in cabal. Tracking issue: https://github.com/haskell/cabal/issues/8684

Hint: this may be caused by trying to build a package that exists in the project directory but is missing from the 'packages' stanza in your cabal project file.

Comment on lines +109 to +110
let targetStrings = if null ts then ["all"] else ts
withContextAndSelectors RejectNoTargets Nothing flags targetStrings globalFlags BuildCommand $ \targetCtx ctx targetSelectors -> do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you require (RejectNoTargets) a list of target selectors.Why?
I would think a command to list targets does not need targets to be specified.

If you want to just list the targets you probably do not need all most of this function, but only the elaborated plan.

withContextAndSelectors is what powers cabal run myscript.hs unless you want to support cabal target myscript.hs you won't need it. If you do not use it, cabal target will only work in a project context. This is what I would expect.

runProjectPreBuildPhase rebuilds the project plan and then uses the function you pass to trim it down to only the required targets. Again, if you just want to list the available targets, you don't need to trim anything. Also it marks the up-to-date targets as 'installed', which means you won't get them from InstallPlan.executionOrder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants