Skip to content

Commit 6eeffc7

Browse files
authored
Merge pull request #5171 from cannorin/opam-tree
Add `opam tree` subcommand
2 parents 559143c + eece100 commit 6eeffc7

20 files changed

+1218
-45
lines changed

doc/man/opam-topics.inc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,24 @@
216216
(with-stdout-to opam-show.1 (run %{bin:opam} show --help=groff)))
217217
(diff opam-show.err %{dep:opam-show.0}))))
218218

219+
(rule
220+
(with-stdout-to opam-why.0 (echo "")))
221+
(rule
222+
(targets opam-why.1 opam-why.err)
223+
(deps using-built-opam)
224+
(action (progn (with-stderr-to opam-why.err
225+
(with-stdout-to opam-why.1 (run %{bin:opam} why --help=groff)))
226+
(diff opam-why.err %{dep:opam-why.0}))))
227+
228+
(rule
229+
(with-stdout-to opam-tree.0 (echo "")))
230+
(rule
231+
(targets opam-tree.1 opam-tree.err)
232+
(deps using-built-opam)
233+
(action (progn (with-stderr-to opam-tree.err
234+
(with-stdout-to opam-tree.1 (run %{bin:opam} tree --help=groff)))
235+
(diff opam-tree.err %{dep:opam-tree.0}))))
236+
219237
(rule
220238
(with-stdout-to opam-search.0 (echo "")))
221239
(rule
@@ -271,6 +289,8 @@
271289
opam-install.1
272290
opam-info.1
273291
opam-show.1
292+
opam-why.1
293+
opam-tree.1
274294
opam-search.1
275295
opam-list.1
276296
opam-init.1))

master_changes.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ users)
3131
* [BUG] Fix spaces in root and switch dirs [#5203 @jonahbeckford]
3232
* Use menu for init setup [#5057 @AltGr; #5217 @dra27]
3333
* Do not show --yes and --no as special global options when using cmdliner >= 1.1 [#5269 @kit-ty-kate]
34+
* ◈ Add `tree` subcommand to display a dependency tree of currently installed packages [#5171 @cannorin - fix #3775]
35+
* ◈ Add `why` subcommand to examine how the versions of currently installed packages get constrained (alias to `tree --rev-deps`) [#5171 @cannorin - fix #3775]
3436

3537
## Plugins
3638
*
@@ -333,6 +335,8 @@ users)
333335
* Add some tests for --best-effort to avoid further regressions when trying to install specific versions of packages [@5261 @kit-ty-kate]
334336
* Add unhelpful conflict error message test [#5270 @kit-ty-kate]
335337
* Add rebuild test [#5258 @rjbou]
338+
* Add test for opam tree command [#5171 @cannorin]
339+
336340
### Engine
337341
* Add `opam-cat` to normalise opam file printing [#4763 @rjbou @dra27] [2.1.0~rc2 #4715]
338342
* Fix meld reftest: open only with failing ones [#4913 @rjbou]
@@ -433,6 +437,9 @@ users)
433437
* `OpamArgTools`: all flag definition takes now a section as a labelled argument [#5275 @rjbou]
434438
* `OpamArg`: all flag definition takes now a section as an optional argument, default is set to `Manpage.s_options` [#5275 @rjbou]
435439

440+
* Add `OpamTreeCommand` [#5171 @cannorin]
441+
* `OpamSolution`: add `dry_run` to simulate the new switch state after applying a solution [#5171 @cannorin]
442+
436443
## opam-repository
437444
* `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]
438445
* New download functions for shared source, old ones kept [#4893 @rjbou]
@@ -489,6 +496,8 @@ users)
489496
* `OpamFile.OPAM`: Add `locked`, file origin and extension, in the record with its modifiers/getter [#5080 @rjbou]
490497
* `OpamFile.OPAM.effective_part`: empty extra-source url if checksum is specified and take first one (as for url) [#5258 @kit-ty-kate]
491498
* `OpamFile.OPAM.effectively_equal`: return true if an extra-source url changes but not its checksum (as for url) [#5258 @kit-ty-kate]
499+
* `OpamFormula`: add generic `formula_to_cnf` and `formula_to_dnf`, and use them in `to_cnf` and `to_dnf` [#5171 @cannorin]
500+
* `OpamFilter`: add `?custom` argument in `to_string` to tweak the output [#5171 @cannorin]
492501

493502
## opam-core
494503
* OpamSystem: avoid calling Unix.environment at top level [#4789 @hannesm]
@@ -512,3 +521,6 @@ users)
512521
* `OpamStd.Sys`: add `all_shells` list of all supported shells [#5217 @dra27]
513522
* `OpamUrl`: add `to_string_w_subpath` to display subpath inside urls (before hash) [#5219 @rjbou]
514523
* `OpamFilename.SubPath`: remove `pretty_string` in favor to `OpamUrl.to_string_w_subpath` [#5219 @rjbou]
524+
* `OpamConsole`: add a `Tree` submodule to draw a unicode/ascii-art tree [#5171 @cannorin]
525+
* `OpamStd.List`: add `find_map_opt` (for ocaml < 4.10) and `fold_left_map` (for ocaml < 4.11) [#5171 @cannorin]
526+
* `OpamCompat`: add `Int.equal` (for ocaml < 4.12)

src/client/opamCommands.ml

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,118 @@ let list ?(force_search=false) cli =
710710
$no_switch $depexts $vars $repos $owns_file $disjunction $search
711711
$silent $no_depexts $package_listing cli $pattern_list)
712712

713+
(* TREE *)
714+
let tree_doc = "Draw the dependency forest of installed packages."
715+
let tree ?(why=false) cli =
716+
let doc = tree_doc in
717+
let build_docs = "TREE BUILDING OPTIONS" in
718+
let filter_docs = "TREE FILTERING OPTIONS" in
719+
let selection_docs = OpamArg.package_selection_section in
720+
let display_docs = OpamArg.package_listing_section in
721+
let man = [
722+
`S Manpage.s_description;
723+
`P "This command displays the forest of currently installed \
724+
packages as a Unicode/ASCII-art.";
725+
`P "Without argument, it draws the dependency forest of packages with no \
726+
dependants. With packages arguments, draws the forest of the specified \
727+
packages. When non-installed packages are specified in the arguments, \
728+
it will try to simulate installing them before drawing the forest.";
729+
`P "When the $(b,--rev-deps) option is used, it draws the \
730+
reverse-dependency forest instead. Without argument, draws the forest \
731+
of packages with no dependencies. With packages arguments, draws the \
732+
forest of the specified packages. Note that non-installed packages are \
733+
ignored when this option is used.";
734+
`P ("When a package appears twice or more in the forest, the second or \
735+
later occurrences of the said package will be marked as a duplicate, \
736+
and be annotated with the $(i,"^OpamTreeCommand.duplicate_symbol^") \
737+
symbol. Any sub-trees rooted from such duplicates will be truncated \
738+
to avoid redundancy.");
739+
`P ("See section $(b,"^filter_docs^") and $(b,"^selection_docs^") for all \
740+
the ways to select the packages to be displayed, and section \
741+
$(b,"^display_docs^") to customise the output format.");
742+
`P "For a flat list of packages which may not be installed, \
743+
see $(b,opam list).";
744+
`S Manpage.s_arguments;
745+
`S build_docs;
746+
`S filter_docs;
747+
`P "These options only take effect when $(i,PACKAGES) are present.";
748+
`S selection_docs;
749+
`S display_docs;
750+
] in
751+
let mode =
752+
let default = OpamTreeCommand.(if why then ReverseDeps else Deps) in
753+
mk_vflag default ~cli ~section:build_docs [
754+
cli_from cli2_2, OpamTreeCommand.Deps, ["deps"],
755+
"Draw a dependency forest, starting from the packages not required by \
756+
any other packages (this is the default).";
757+
cli_from cli2_2, OpamTreeCommand.ReverseDeps, ["rev-deps"],
758+
"Draw a reverse-dependency forest, starting from the packages which \
759+
have no dependencies.";
760+
]
761+
in
762+
let filter =
763+
let default = OpamTreeCommand.Roots_from in
764+
mk_vflag default ~cli ~section:filter_docs [
765+
cli_from cli2_2, OpamTreeCommand.Roots_from, ["roots-from"],
766+
"Display only the trees which roots from one of the $(i,PACKAGES) \
767+
(this is the default).";
768+
cli_from cli2_2, OpamTreeCommand.Leads_to, ["leads-to"],
769+
"Display only the branches which leads to one of the $(i,PACKAGES).";
770+
]
771+
in
772+
let post =
773+
mk_flag ~cli (cli_from cli2_2) ["post"] ~section:selection_docs
774+
"Include dependencies tagged as $(i,post)."
775+
in
776+
let dev =
777+
mk_flag ~cli (cli_from cli2_2) ["dev"] ~section:selection_docs
778+
"Include development packages in dependencies."
779+
in
780+
let doc_flag =
781+
mk_flag ~cli (cli_from cli2_2) ["doc";"with-doc"] ~section:selection_docs
782+
"Include doc-only dependencies."
783+
in
784+
let test =
785+
mk_flag ~cli (cli_from cli2_2) ["t";"test";"with-test"]
786+
~section:selection_docs
787+
"Include test-only dependencies."
788+
in
789+
let tools =
790+
mk_flag ~cli (cli_from cli2_2) ["with-tools"] ~section:selection_docs
791+
"Include development only dependencies."
792+
in
793+
let no_cstr =
794+
mk_flag ~cli (cli_from cli2_2) ["no-constraint"] ~section:display_docs
795+
"Do not display the version constraints e.g. $(i,(>= 1.0.0))."
796+
in
797+
let no_switch =
798+
mk_flag ~cli (cli_from cli2_2) ["no-switch"] ~section:selection_docs
799+
"Ignore active switch and simulate installing packages from an empty \
800+
switch to draw the forest"
801+
in
802+
let tree global_options mode filter post dev doc test tools no_constraint
803+
no_switch names () =
804+
if names = [] && no_switch then
805+
`Error
806+
(true, "--no-switch can't be used without specifying a name")
807+
else
808+
(apply_global_options cli global_options;
809+
OpamGlobalState.with_ `Lock_none @@ fun gt ->
810+
OpamSwitchState.with_ `Lock_none gt @@ fun st ->
811+
let tog = OpamListCommand.{
812+
post; test; doc; dev; tools;
813+
recursive = false;
814+
depopts = false;
815+
build = true;
816+
} in
817+
OpamTreeCommand.run st tog ~no_constraint ~no_switch mode filter names;
818+
`Ok ())
819+
in
820+
mk_command_ret ~cli (cli_from cli2_2) "tree" ~doc ~man
821+
Term.(const tree $global_options cli $mode $filter
822+
$post $dev $doc_flag $test $tools
823+
$no_cstr $no_switch
824+
$name_list)
713825

714826
(* SHOW *)
715827
let show_doc = "Display information about specific packages."
@@ -4192,6 +4304,8 @@ let commands cli =
41924304
init cli;
41934305
list cli;
41944306
make_command_alias ~cli (list ~force_search:true cli) ~options:" --search" "search";
4307+
tree cli;
4308+
make_command_alias ~cli (tree ~why:true cli) ~options:" --rev-deps" "why";
41954309
show; make_command_alias ~cli show "info";
41964310
install cli;
41974311
remove; make_command_alias ~cli remove "uninstall";

src/client/opamSolution.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,9 @@ let simulate_new_state state t =
10391039
t state.installed in
10401040
{ state with installed }
10411041

1042+
let dry_run state solution =
1043+
simulate_new_state state (OpamSolver.get_atomic_action_graph solution)
1044+
10421045
(* Ask confirmation whenever the packages to modify are not exactly
10431046
the packages in the user request *)
10441047
let confirmation ?ask requested solution =

src/client/opamSolution.mli

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ val check_solution:
7171
(solution_result, 'conflict) result ->
7272
unit
7373

74+
(** Simulate the new [switch_state] after applying the [solution]
75+
without actually performing the action(s) on disk. *)
76+
val dry_run: 'a switch_state -> OpamSolver.solution -> 'a switch_state
77+
7478
(* Install external dependencies of the given package set, according the depext
7579
configuration. If [confirm] is false, install commands are directly
7680
launched, without asking user (used by the `--depext-only` option). If

0 commit comments

Comments
 (0)