From 9078cace6e22d4c2811ed7f35e7cf74607f3bd22 Mon Sep 17 00:00:00 2001 From: Richard L Ford Date: Wed, 22 Feb 2023 15:04:30 -0500 Subject: [PATCH] Add tests of building toplevel that can load plugins One test show successful plugin loading in a toplevel. The other test shows the error when you use Dynlink.loadfile (via dune-site.dynlink) in a toplevel. Also, handle moved load_file function. Prior to OCaml 4.13.0, the load_file function was in Topdirs. Starting with OCaml 4.13.0, the load_file function moved to Toploop. In order to find it open both these modules, suppressing the warning for unused open, and then reference load_file unqualified. Also add change log entry. Signed-off-by: Richard L Ford --- CHANGES.md | 3 + .../src/plugins/linker/toplevel/linker.ml | 5 +- .../test-cases/toplevel-plugin-fail.t | 185 +++++++++++++++++ .../test-cases/toplevel-plugin.t | 196 ++++++++++++++++++ 4 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 test/blackbox-tests/test-cases/toplevel-plugin-fail.t create mode 100644 test/blackbox-tests/test-cases/toplevel-plugin.t diff --git a/CHANGES.md b/CHANGES.md index 1e7fdf0c92c6..d74d814985e2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ Unreleased ---------- +- Adds support for loading plugins in toplevels (#6082, fixes #6081, + @ivg, @richardlford) + - Support commands that output 8-bit and 24-bit colors in the terminal (#7188, @Alizter) diff --git a/otherlibs/site/src/plugins/linker/toplevel/linker.ml b/otherlibs/site/src/plugins/linker/toplevel/linker.ml index 3001b70181e8..cf445e275db6 100644 --- a/otherlibs/site/src/plugins/linker/toplevel/linker.ml +++ b/otherlibs/site/src/plugins/linker/toplevel/linker.ml @@ -1,7 +1,10 @@ +open Topdirs [@@ocaml.warning "-33"] +open Toploop [@@ocaml.warning "-33"] + let load filename = let buf = Buffer.create 16 in let ppf = Format.formatter_of_buffer buf in - match Toploop.load_file ppf filename with + match load_file ppf filename with | true -> () | false -> Format.pp_print_flush ppf (); diff --git a/test/blackbox-tests/test-cases/toplevel-plugin-fail.t b/test/blackbox-tests/test-cases/toplevel-plugin-fail.t new file mode 100644 index 000000000000..ac3b1962103f --- /dev/null +++ b/test/blackbox-tests/test-cases/toplevel-plugin-fail.t @@ -0,0 +1,185 @@ +Testsuite for (toplevel that loads plugins). This version +uses dune-site.dynlink which uses Dynlink.loadfile. +This is not allowed in top-levels, so it fails. + + $ cat > dune-project < (lang dune 3.7) + > (using dune_site 0.1) + > (name top_with_plugins) + > (wrapped_executables false) + > (map_workspace_root false) + > + > (package + > (name top_with_plugins) + > (sites (lib top_plugins))) + > EOF + + $ cat > dune < (executable + > (public_name top_with_plugins) + > (name top_with_plugins) + > (modes byte) + > (flags :standard -safe-string) + > (modules sites top_with_plugins) + > (link_flags (-linkall)) + > (libraries compiler-libs.toplevel + > top_with_plugins.register dune-site dune-site.plugins + > dune-site.dynlink findlib.dynload)) + > + > (library + > (public_name top_with_plugins.register) + > (modes byte) + > (name registration) + > (modules registration)) + > + > (generate_sites_module + > (module sites) + > (plugins (top_with_plugins top_plugins))) + > EOF + + $ cat > top_with_plugins.ml < let main () = + > print_endline "\nMain app really starts..."; + > (* load all the available plugins *) + > Sites.Plugins.Top_plugins.load_all (); + > print_endline "Main app after loading plugins..."; + > (* Execute the code registered by the plugins *) + > print_endline "Main app executing registered plugins..."; + > Queue.iter (fun f -> f ()) Registration.todo; + > print_endline "Main app after executing registered plugins..."; + > exit (Topmain.main ()) + > + > let () = + > main() + > EOF + + $ cat > registration.ml < let todo : (unit -> unit) Queue.t = Queue.create () + > let register f = + > print_endline "In register"; + > Queue.add f todo; + > print_endline "Done in register"; + > EOF + + $ mkdir plugin1 + $ cat > plugin1/dune-project < (lang dune 3.7) + > (using dune_site 0.1) + > + > (generate_opam_files true) + > (wrapped_executables false) + > (map_workspace_root false) + > (package + > (name top-plugin1)) + > EOF + + $ cat > plugin1/dune < (library + > (public_name top-plugin1.plugin1_impl) + > (modes byte) + > (name plugin1_impl) + > (modules plugin1_impl) + > (libraries top_with_plugins.register)) + > + > (plugin + > (name plugin1) + > (libraries top-plugin1.plugin1_impl) + > (site (top_with_plugins top_plugins))) + > EOF + + $ cat > plugin1/plugin1_impl.ml < let myfun () = + > print_endline "Plugin1 is doing something..." + > + > let () = + > print_endline "Registration of Plugin1"; + > Registration.register myfun; + > print_endline "Done with registration of Plugin1"; + > EOF + + $ mkdir plugin2 + $ cat > plugin2/dune-project < (lang dune 3.7) + > (using dune_site 0.1) + > + > (generate_opam_files true) + > (wrapped_executables false) + > (map_workspace_root false) + > (package + > (name top-plugin2)) + > EOF + + $ cat > plugin2/dune < (library + > (public_name top-plugin2.plugin2_impl) + > (modes byte) + > (name plugin2_impl) + > (modules plugin2_impl) + > (libraries top_with_plugins.register)) + > + > (plugin + > (name plugin2) + > (libraries top-plugin2.plugin2_impl) + > (site (top_with_plugins top_plugins))) + > EOF + + $ cat > plugin2/plugin2_impl.ml < let myfun () = + > print_endline "Plugin2 is doing something..." + > + > let () = + > print_endline "Registration of Plugin2"; + > Registration.register myfun; + > print_endline "Done with registration of Plugin2"; + > EOF + + $ dune build --display short @all 2>&1 | dune_cmd sanitize + ocamldep .top_with_plugins.eobjs/top_with_plugins.impl.d + ocamlc .registration.objs/byte/registration.{cmi,cmo,cmt} + ocamlc .top_with_plugins.eobjs/byte/dune_site__Dune_site_data.{cmo,cmt} + ocamlc .top_with_plugins.eobjs/byte/dune_site_plugins__Dune_site_plugins_data.{cmo,cmt} + ocamlc .top_with_plugins.eobjs/byte/findlib_initl.{cmi,cmo,cmt} + ocamldep .top_with_plugins.eobjs/sites.impl.d + ocamlc registration.cma + ocamlc plugin1/.plugin1_impl.objs/byte/plugin1_impl.{cmi,cmo,cmt} + ocamlc plugin2/.plugin2_impl.objs/byte/plugin2_impl.{cmi,cmo,cmt} + ocamlc .top_with_plugins.eobjs/byte/sites.{cmi,cmo,cmt} + ocamldep .top_with_plugins.eobjs/top_with_plugins.intf.d + ocamlc plugin1/plugin1_impl.cma + ocamlc plugin2/plugin2_impl.cma + ocamlc .top_with_plugins.eobjs/byte/top_with_plugins.{cmi,cmti} + ocamlc .top_with_plugins.eobjs/byte/top_with_plugins.{cmo,cmt} + ocamlc top_with_plugins.bc + ocamlc top_with_plugins.exe + $ dune install --prefix _install --display short + Installing _install/lib/top-plugin1/META + Installing _install/lib/top-plugin1/dune-package + Installing _install/lib/top-plugin1/plugin1_impl/plugin1_impl.cma + Installing _install/lib/top-plugin1/plugin1_impl/plugin1_impl.cmi + Installing _install/lib/top-plugin1/plugin1_impl/plugin1_impl.cmt + Installing _install/lib/top-plugin1/plugin1_impl/plugin1_impl.ml + Installing _install/lib/top_with_plugins/top_plugins/plugin1/META + Installing _install/lib/top-plugin2/META + Installing _install/lib/top-plugin2/dune-package + Installing _install/lib/top-plugin2/plugin2_impl/plugin2_impl.cma + Installing _install/lib/top-plugin2/plugin2_impl/plugin2_impl.cmi + Installing _install/lib/top-plugin2/plugin2_impl/plugin2_impl.cmt + Installing _install/lib/top-plugin2/plugin2_impl/plugin2_impl.ml + Installing _install/lib/top_with_plugins/top_plugins/plugin2/META + Installing _install/lib/top_with_plugins/META + Installing _install/lib/top_with_plugins/dune-package + Installing _install/lib/top_with_plugins/register/registration.cma + Installing _install/lib/top_with_plugins/register/registration.cmi + Installing _install/lib/top_with_plugins/register/registration.cmt + Installing _install/lib/top_with_plugins/register/registration.ml + Installing _install/bin/top_with_plugins + $ export OCAMLPATH=$PWD/_install/lib + $ ./_install/bin/top_with_plugins -no-version < 2+2;; + > #quit;; + > EOF + + Main app really starts... + Fatal error: exception Invalid_argument("The dynlink.cma library cannot be used inside the OCaml toplevel") + [2] + diff --git a/test/blackbox-tests/test-cases/toplevel-plugin.t b/test/blackbox-tests/test-cases/toplevel-plugin.t new file mode 100644 index 000000000000..590113839ea1 --- /dev/null +++ b/test/blackbox-tests/test-cases/toplevel-plugin.t @@ -0,0 +1,196 @@ +Testsuite for (toplevel that loads plugins). + + $ cat > dune-project < (lang dune 3.7) + > (using dune_site 0.1) + > (name top_with_plugins) + > (wrapped_executables false) + > (map_workspace_root false) + > + > (package + > (name top_with_plugins) + > (sites (lib top_plugins))) + > EOF + + $ cat > dune < (executable + > (public_name top_with_plugins) + > (name top_with_plugins) + > (modes byte) + > (flags :standard -safe-string) + > (modules sites top_with_plugins) + > (link_flags (-linkall)) + > (libraries compiler-libs.toplevel + > top_with_plugins.register dune-site dune-site.plugins + > dune-site.toplevel findlib.dynload)) + > + > (library + > (public_name top_with_plugins.register) + > (modes byte) + > (name registration) + > (modules registration)) + > + > (generate_sites_module + > (module sites) + > (plugins (top_with_plugins top_plugins))) + > EOF + + $ cat > top_with_plugins.ml < let main () = + > print_endline "\nMain app really starts..."; + > (* load all the available plugins *) + > Sites.Plugins.Top_plugins.load_all (); + > print_endline "Main app after loading plugins..."; + > (* Execute the code registered by the plugins *) + > print_endline "Main app executing registered plugins..."; + > Queue.iter (fun f -> f ()) Registration.todo; + > print_endline "Main app after executing registered plugins..."; + > exit (Topmain.main ()) + > + > let () = + > main() + > EOF + + $ cat > registration.ml < let todo : (unit -> unit) Queue.t = Queue.create () + > let register f = + > print_endline "In register"; + > Queue.add f todo; + > print_endline "Done in register"; + > EOF + + $ mkdir plugin1 + $ cat > plugin1/dune-project < (lang dune 3.7) + > (using dune_site 0.1) + > + > (generate_opam_files true) + > (wrapped_executables false) + > (map_workspace_root false) + > (package + > (name top-plugin1)) + > EOF + + $ cat > plugin1/dune < (library + > (public_name top-plugin1.plugin1_impl) + > (modes byte) + > (name plugin1_impl) + > (modules plugin1_impl) + > (libraries top_with_plugins.register)) + > + > (plugin + > (name plugin1) + > (libraries top-plugin1.plugin1_impl) + > (site (top_with_plugins top_plugins))) + > EOF + + $ cat > plugin1/plugin1_impl.ml < let myfun () = + > print_endline "Plugin1 is doing something..." + > + > let () = + > print_endline "Registration of Plugin1"; + > Registration.register myfun; + > print_endline "Done with registration of Plugin1"; + > EOF + + $ mkdir plugin2 + $ cat > plugin2/dune-project < (lang dune 3.7) + > (using dune_site 0.1) + > + > (generate_opam_files true) + > (wrapped_executables false) + > (map_workspace_root false) + > (package + > (name top-plugin2)) + > EOF + + $ cat > plugin2/dune < (library + > (public_name top-plugin2.plugin2_impl) + > (modes byte) + > (name plugin2_impl) + > (modules plugin2_impl) + > (libraries top_with_plugins.register)) + > + > (plugin + > (name plugin2) + > (libraries top-plugin2.plugin2_impl) + > (site (top_with_plugins top_plugins))) + > EOF + + $ cat > plugin2/plugin2_impl.ml < let myfun () = + > print_endline "Plugin2 is doing something..." + > + > let () = + > print_endline "Registration of Plugin2"; + > Registration.register myfun; + > print_endline "Done with registration of Plugin2"; + > EOF + + $ dune build --display short @all 2>&1 | dune_cmd sanitize + ocamldep .top_with_plugins.eobjs/top_with_plugins.impl.d + ocamlc .registration.objs/byte/registration.{cmi,cmo,cmt} + ocamlc .top_with_plugins.eobjs/byte/dune_site__Dune_site_data.{cmo,cmt} + ocamlc .top_with_plugins.eobjs/byte/dune_site_plugins__Dune_site_plugins_data.{cmo,cmt} + ocamlc .top_with_plugins.eobjs/byte/findlib_initl.{cmi,cmo,cmt} + ocamldep .top_with_plugins.eobjs/sites.impl.d + ocamlc registration.cma + ocamlc plugin1/.plugin1_impl.objs/byte/plugin1_impl.{cmi,cmo,cmt} + ocamlc plugin2/.plugin2_impl.objs/byte/plugin2_impl.{cmi,cmo,cmt} + ocamlc .top_with_plugins.eobjs/byte/sites.{cmi,cmo,cmt} + ocamldep .top_with_plugins.eobjs/top_with_plugins.intf.d + ocamlc plugin1/plugin1_impl.cma + ocamlc plugin2/plugin2_impl.cma + ocamlc .top_with_plugins.eobjs/byte/top_with_plugins.{cmi,cmti} + ocamlc .top_with_plugins.eobjs/byte/top_with_plugins.{cmo,cmt} + ocamlc top_with_plugins.bc + ocamlc top_with_plugins.exe + $ dune install --prefix _install --display short + Installing _install/lib/top-plugin1/META + Installing _install/lib/top-plugin1/dune-package + Installing _install/lib/top-plugin1/plugin1_impl/plugin1_impl.cma + Installing _install/lib/top-plugin1/plugin1_impl/plugin1_impl.cmi + Installing _install/lib/top-plugin1/plugin1_impl/plugin1_impl.cmt + Installing _install/lib/top-plugin1/plugin1_impl/plugin1_impl.ml + Installing _install/lib/top_with_plugins/top_plugins/plugin1/META + Installing _install/lib/top-plugin2/META + Installing _install/lib/top-plugin2/dune-package + Installing _install/lib/top-plugin2/plugin2_impl/plugin2_impl.cma + Installing _install/lib/top-plugin2/plugin2_impl/plugin2_impl.cmi + Installing _install/lib/top-plugin2/plugin2_impl/plugin2_impl.cmt + Installing _install/lib/top-plugin2/plugin2_impl/plugin2_impl.ml + Installing _install/lib/top_with_plugins/top_plugins/plugin2/META + Installing _install/lib/top_with_plugins/META + Installing _install/lib/top_with_plugins/dune-package + Installing _install/lib/top_with_plugins/register/registration.cma + Installing _install/lib/top_with_plugins/register/registration.cmi + Installing _install/lib/top_with_plugins/register/registration.cmt + Installing _install/lib/top_with_plugins/register/registration.ml + Installing _install/bin/top_with_plugins + $ export OCAMLPATH=$PWD/_install/lib + $ ./_install/bin/top_with_plugins -no-version < 2+2;; + > #quit;; + > EOF + + Main app really starts... + Registration of Plugin1 + In register + Done in register + Done with registration of Plugin1 + Registration of Plugin2 + In register + Done in register + Done with registration of Plugin2 + Main app after loading plugins... + Main app executing registered plugins... + Plugin1 is doing something... + Plugin2 is doing something... + Main app after executing registered plugins... + # - : int = 4 + # +