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 opam tree subcommand #5171

Merged
merged 11 commits into from
Sep 12, 2022
20 changes: 20 additions & 0 deletions doc/man/opam-topics.inc
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,24 @@
(with-stdout-to opam-show.1 (run %{bin:opam} show --help=groff)))
(diff opam-show.err %{dep:opam-show.0}))))

(rule
(with-stdout-to opam-why.0 (echo "")))
(rule
(targets opam-why.1 opam-why.err)
(deps using-built-opam)
(action (progn (with-stderr-to opam-why.err
(with-stdout-to opam-why.1 (run %{bin:opam} why --help=groff)))
(diff opam-why.err %{dep:opam-why.0}))))

(rule
(with-stdout-to opam-tree.0 (echo "")))
(rule
(targets opam-tree.1 opam-tree.err)
(deps using-built-opam)
(action (progn (with-stderr-to opam-tree.err
(with-stdout-to opam-tree.1 (run %{bin:opam} tree --help=groff)))
(diff opam-tree.err %{dep:opam-tree.0}))))

(rule
(with-stdout-to opam-search.0 (echo "")))
(rule
Expand Down Expand Up @@ -271,6 +289,8 @@
opam-install.1
opam-info.1
opam-show.1
opam-why.1
opam-tree.1
opam-search.1
opam-list.1
opam-init.1))
12 changes: 12 additions & 0 deletions master_changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ users)
* [BUG] Fix spaces in root and switch dirs [#5203 @jonahbeckford]
* Use menu for init setup [#5057 @AltGr; #5217 @dra27]
* Do not show --yes and --no as special global options when using cmdliner >= 1.1 [#5269 @kit-ty-kate]
* ◈ Add `tree` subcommand to display a dependency tree of currently installed packages [#5171 @cannorin - fix #3775]
* ◈ Add `why` subcommand to examine how the versions of currently installed packages get constrained (alias to `tree --rev-deps`) [#5171 @cannorin - fix #3775]

## Plugins
*
Expand Down Expand Up @@ -333,6 +335,8 @@ users)
* Add some tests for --best-effort to avoid further regressions when trying to install specific versions of packages [@5261 @kit-ty-kate]
* Add unhelpful conflict error message test [#5270 @kit-ty-kate]
* Add rebuild test [#5258 @rjbou]
* Add test for opam tree command [#5171 @cannorin]

### Engine
* Add `opam-cat` to normalise opam file printing [#4763 @rjbou @dra27] [2.1.0~rc2 #4715]
* Fix meld reftest: open only with failing ones [#4913 @rjbou]
Expand Down Expand Up @@ -433,6 +437,9 @@ users)
* `OpamArgTools`: all flag definition takes now a section as a labelled argument [#5275 @rjbou]
* `OpamArg`: all flag definition takes now a section as an optional argument, default is set to `Manpage.s_options` [#5275 @rjbou]

* Add `OpamTreeCommand` [#5171 @cannorin]
* `OpamSolution`: add `dry_run` to simulate the new switch state after applying a solution [#5171 @cannorin]

## opam-repository
* `OpamRepositoryConfig`: add in config record `repo_tarring` field and as an argument to config functions, and a new constructor `REPOSITORYTARRING` in `E` environment module and its access function [#5015 @rjbou]
* New download functions for shared source, old ones kept [#4893 @rjbou]
Expand Down Expand Up @@ -489,6 +496,8 @@ users)
* `OpamFile.OPAM`: Add `locked`, file origin and extension, in the record with its modifiers/getter [#5080 @rjbou]
* `OpamFile.OPAM.effective_part`: empty extra-source url if checksum is specified and take first one (as for url) [#5258 @kit-ty-kate]
* `OpamFile.OPAM.effectively_equal`: return true if an extra-source url changes but not its checksum (as for url) [#5258 @kit-ty-kate]
* `OpamFormula`: add generic `formula_to_cnf` and `formula_to_dnf`, and use them in `to_cnf` and `to_dnf` [#5171 @cannorin]
* `OpamFilter`: add `?custom` argument in `to_string` to tweak the output [#5171 @cannorin]

## opam-core
* OpamSystem: avoid calling Unix.environment at top level [#4789 @hannesm]
Expand All @@ -512,3 +521,6 @@ users)
* `OpamStd.Sys`: add `all_shells` list of all supported shells [#5217 @dra27]
* `OpamUrl`: add `to_string_w_subpath` to display subpath inside urls (before hash) [#5219 @rjbou]
* `OpamFilename.SubPath`: remove `pretty_string` in favor to `OpamUrl.to_string_w_subpath` [#5219 @rjbou]
* `OpamConsole`: add a `Tree` submodule to draw a unicode/ascii-art tree [#5171 @cannorin]
* `OpamStd.List`: add `find_map_opt` (for ocaml < 4.10) and `fold_left_map` (for ocaml < 4.11) [#5171 @cannorin]
* `OpamCompat`: add `Int.equal` (for ocaml < 4.12)
114 changes: 114 additions & 0 deletions src/client/opamCommands.ml
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,118 @@ let list ?(force_search=false) cli =
$no_switch $depexts $vars $repos $owns_file $disjunction $search
$silent $no_depexts $package_listing cli $pattern_list)

(* TREE *)
let tree_doc = "Draw the dependency forest of installed packages."
let tree ?(why=false) cli =
let doc = tree_doc in
let build_docs = "TREE BUILDING OPTIONS" in
let filter_docs = "TREE FILTERING OPTIONS" in
let selection_docs = OpamArg.package_selection_section in
let display_docs = OpamArg.package_listing_section in
let man = [
`S Manpage.s_description;
`P "This command displays the forest of currently installed \
packages as a Unicode/ASCII-art.";
`P "Without argument, it draws the dependency forest of packages with no \
dependants. With packages arguments, draws the forest of the specified \
packages. When non-installed packages are specified in the arguments, \
it will try to simulate installing them before drawing the forest.";
Comment on lines +725 to +728
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we add a word on --no-switch here ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as --no-switch is well documented, I don't think it's necessary.

`P "When the $(b,--rev-deps) option is used, it draws the \
reverse-dependency forest instead. Without argument, draws the forest \
of packages with no dependencies. With packages arguments, draws the \
forest of the specified packages. Note that non-installed packages are \
ignored when this option is used.";
`P ("When a package appears twice or more in the forest, the second or \
later occurrences of the said package will be marked as a duplicate, \
and be annotated with the $(i,"^OpamTreeCommand.duplicate_symbol^") \
symbol. Any sub-trees rooted from such duplicates will be truncated \
to avoid redundancy.");
`P ("See section $(b,"^filter_docs^") and $(b,"^selection_docs^") for all \
the ways to select the packages to be displayed, and section \
$(b,"^display_docs^") to customise the output format.");
`P "For a flat list of packages which may not be installed, \
see $(b,opam list).";
`S Manpage.s_arguments;
`S build_docs;
`S filter_docs;
`P "These options only take effect when $(i,PACKAGES) are present.";
`S selection_docs;
`S display_docs;
] in
let mode =
let default = OpamTreeCommand.(if why then ReverseDeps else Deps) in
mk_vflag default ~cli ~section:build_docs [
cli_from cli2_2, OpamTreeCommand.Deps, ["deps"],
"Draw a dependency forest, starting from the packages not required by \
any other packages (this is the default).";
cli_from cli2_2, OpamTreeCommand.ReverseDeps, ["rev-deps"],
"Draw a reverse-dependency forest, starting from the packages which \
have no dependencies.";
]
in
let filter =
let default = OpamTreeCommand.Roots_from in
mk_vflag default ~cli ~section:filter_docs [
cli_from cli2_2, OpamTreeCommand.Roots_from, ["roots-from"],
"Display only the trees which roots from one of the $(i,PACKAGES) \
(this is the default).";
cli_from cli2_2, OpamTreeCommand.Leads_to, ["leads-to"],
"Display only the branches which leads to one of the $(i,PACKAGES).";
]
in
let post =
mk_flag ~cli (cli_from cli2_2) ["post"] ~section:selection_docs
"Include dependencies tagged as $(i,post)."
in
let dev =
mk_flag ~cli (cli_from cli2_2) ["dev"] ~section:selection_docs
"Include development packages in dependencies."
in
let doc_flag =
mk_flag ~cli (cli_from cli2_2) ["doc";"with-doc"] ~section:selection_docs
"Include doc-only dependencies."
in
let test =
mk_flag ~cli (cli_from cli2_2) ["t";"test";"with-test"]
~section:selection_docs
"Include test-only dependencies."
in
let tools =
mk_flag ~cli (cli_from cli2_2) ["with-tools"] ~section:selection_docs
"Include development only dependencies."
in
let no_cstr =
mk_flag ~cli (cli_from cli2_2) ["no-constraint"] ~section:display_docs
"Do not display the version constraints e.g. $(i,(>= 1.0.0))."
in
let no_switch =
mk_flag ~cli (cli_from cli2_2) ["no-switch"] ~section:selection_docs
"Ignore active switch and simulate installing packages from an empty \
switch to draw the forest"
in
let tree global_options mode filter post dev doc test tools no_constraint
no_switch names () =
if names = [] && no_switch then
`Error
(true, "--no-switch can't be used without specifying a name")
else
(apply_global_options cli global_options;
OpamGlobalState.with_ `Lock_none @@ fun gt ->
OpamSwitchState.with_ `Lock_none gt @@ fun st ->
let tog = OpamListCommand.{
post; test; doc; dev; tools;
recursive = false;
depopts = false;
build = true;
} in
OpamTreeCommand.run st tog ~no_constraint ~no_switch mode filter names;
`Ok ())
in
mk_command_ret ~cli (cli_from cli2_2) "tree" ~doc ~man
Term.(const tree $global_options cli $mode $filter
$post $dev $doc_flag $test $tools
$no_cstr $no_switch
$name_list)

(* SHOW *)
let show_doc = "Display information about specific packages."
Expand Down Expand Up @@ -4192,6 +4304,8 @@ let commands cli =
init cli;
list cli;
make_command_alias ~cli (list ~force_search:true cli) ~options:" --search" "search";
tree cli;
make_command_alias ~cli (tree ~why:true cli) ~options:" --rev-deps" "why";
show; make_command_alias ~cli show "info";
install cli;
remove; make_command_alias ~cli remove "uninstall";
Expand Down
3 changes: 3 additions & 0 deletions src/client/opamSolution.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,9 @@ let simulate_new_state state t =
t state.installed in
{ state with installed }

let dry_run state solution =
simulate_new_state state (OpamSolver.get_atomic_action_graph solution)

(* Ask confirmation whenever the packages to modify are not exactly
the packages in the user request *)
let confirmation ?ask requested solution =
Expand Down
4 changes: 4 additions & 0 deletions src/client/opamSolution.mli
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ val check_solution:
(solution_result, 'conflict) result ->
unit

(** Simulate the new [switch_state] after applying the [solution]
without actually performing the action(s) on disk. *)
val dry_run: 'a switch_state -> OpamSolver.solution -> 'a switch_state

(* Install external dependencies of the given package set, according the depext
configuration. If [confirm] is false, install commands are directly
launched, without asking user (used by the `--depext-only` option). If
Expand Down
Loading