-
Notifications
You must be signed in to change notification settings - Fork 411
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 support for loading libraries from toplevel #2952
Changes from all commits
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 |
---|---|---|
@@ -0,0 +1,58 @@ | ||
open Stdune | ||
open Import | ||
|
||
let doc = | ||
"Print a list of toplevel directives for including directories and loading \ | ||
cma files." | ||
|
||
let man = | ||
[ `S "DESCRIPTION" | ||
; `P | ||
{|Print a list of toplevel directives for including directories and loading cma files.|} | ||
; `P | ||
{|The output of $(b,dune toplevel-init-file) should be evaluated in a toplevel | ||
to make a library available there.|} | ||
; `Blocks Common.help_secs | ||
] | ||
|
||
let info = Term.info "top" ~doc ~man | ||
|
||
let link_deps link ~lib_config = | ||
List.concat_map link ~f:(fun t -> | ||
Dune.Lib.link_deps t Dune.Link_mode.Byte lib_config) | ||
|
||
let term = | ||
let+ common = Common.term | ||
and+ dir = Arg.(value & pos 0 string "" & Arg.info [] ~docv:"DIR") | ||
and+ ctx_name = | ||
Common.context_arg ~doc:{|Select context where to build/run utop.|} | ||
in | ||
Common.set_common common ~targets:[]; | ||
Scheduler.go ~common (fun () -> | ||
let open Fiber.O in | ||
let* setup = Import.Main.setup common in | ||
let sctx = | ||
Dune.Context_name.Map.find setup.scontexts ctx_name |> Option.value_exn | ||
in | ||
let dir = | ||
Path.Build.relative | ||
(Super_context.build_dir sctx) | ||
(Common.prefix_target common dir) | ||
in | ||
let scope = Super_context.find_scope_by_dir sctx dir in | ||
let db = Dune.Scope.libs scope in | ||
let libs = Dune.Utop.libs_under_dir sctx ~db ~dir:(Path.build dir) in | ||
let requires = Dune.Lib.closure ~linking:true libs |> Result.ok_exn in | ||
let include_paths = Dune.Lib.L.include_paths requires in | ||
let lib_config = sctx |> Super_context.context |> Context.lib_config in | ||
let files = link_deps requires ~lib_config in | ||
let* () = do_build (List.map files ~f:(fun f -> Target.File f)) in | ||
let files_to_load = | ||
List.filter files ~f:(fun p -> | ||
let ext = Path.extension p in | ||
ext = Dune.Mode.compiled_lib_ext Byte || ext = Dune.Cm_kind.ext Cmo) | ||
in | ||
Dune.Toplevel.print_toplevel_init_file ~include_paths ~files_to_load; | ||
Fiber.return ()) | ||
|
||
let command = (term, info) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,3 +29,4 @@ Welcome to dune's documentation! | |
known-issues | ||
migration | ||
caching | ||
toplevel-integration |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
******************** | ||
Toplevel integration | ||
******************** | ||
|
||
It's possible to load dune projects in any toplevel. This is achieved in two stages. | ||
|
||
First, `dune toplevel-init-file` builds the project and produces a list of toplevel pragmas | ||
(#directory and #load). Copying the output of this command to a toplevel lets you | ||
interact with the project's modules. | ||
|
||
Second, to enhance usability, dune also provides a toplevel script, which does the above | ||
manual work for you. To use it, make sure to have `topfind` available in your toplevel by | ||
invoking `#use "topfind";;`. Afterwards you can run `#use "dune";;` and your | ||
modules should be available. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
Test toplevel-init-file on a tiny project | ||
---------------------------------------------------- | ||
$ cat >dune-project <<EOF | ||
> (lang dune 2.1) | ||
> (name test) | ||
> EOF | ||
$ cat >dune <<EOF | ||
> (library | ||
> (name test) | ||
> (public_name test)) | ||
> EOF | ||
$ touch test.opam | ||
$ cat >main.ml <<EOF | ||
> let hello () = print_endline "hello" | ||
> EOF | ||
|
||
$ dune top | ||
#directory "$TESTCASE_ROOT/_build/default/.test.objs/byte";; | ||
#directory "$TESTCASE_ROOT/_build/default/.test.objs/native";; | ||
#load "$TESTCASE_ROOT/_build/default/test.cma";; | ||
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. Could you add a test testing the whole story? i.e. doing
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. Okay, I have several questions 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 a bit of research and right now, packages that install toplevel scripts such as
On the dune side, dune already extends a few environment variable so that when running dune or other tools inside dune, various tools will look for things in Then, in the test Regarding the error case, I was thinking of a case where compilation fails. For instance, what does the user see if one of the ml file 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. Thanks for all this great info and sorry about my (usual) late response! Here's what I've done for now. Since there are still several unknowns, I've also had to make several questionable choices.
Please let me know what you think about this. In case I don't get around to this again for a week, I wish you a very Merry Christmas and thanks again for all your help! 🎄 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. Looking at those errors more closely, they actually reference my local paths, so they're definitely wrong. How do you handle test errors containing local paths? And more importantly, what kind of error behavior should we expect from running 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. Just back from holidays. Thanks, hope you had a nice Christmas too! For 2, that seems reasonable. For 3, we can do the same as the #directory "+compiler-libs";;
...
#remove_directory "+compiler-libs";; For 4, I was surprised by this output as normally Dune doesn't print the full compilation command line if it detects that the error messages already points to a file, i.e. is of the form let show_full_command_on_error () =
inside_dune || inside_ci || !Clflags.always_show_command_line @rgrinberg, I don't remember the details. Was the |
||
|
||
$ ocaml -stdin <<EOF | ||
> #use "topfind";; | ||
> #use "use_output_compat";; | ||
> #use_output "dune top";; | ||
> Test.Main.hello ();; | ||
> EOF | ||
hello | ||
|
||
$ cat >error.ml <<EOF | ||
> let oops () = undefined_function () | ||
> EOF | ||
|
||
$ dune top | ||
File "error.ml", line 1, characters 14-32: | ||
1 | let oops () = undefined_function () | ||
^^^^^^^^^^^^^^^^^^ | ||
Error: Unbound value undefined_function | ||
[1] | ||
|
||
$ ocaml -stdin <<EOF | ||
> #use "topfind";; | ||
> #use "use_output_compat";; | ||
> #use_output "dune top";; | ||
> EOF | ||
File "error.ml", line 1, characters 14-32: | ||
1 | let oops () = undefined_function () | ||
^^^^^^^^^^^^^^^^^^ | ||
Error: Unbound value undefined_function | ||
Command exited with code 1. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
(* -*- tuareg -*- *) | ||
|
||
let try_finally ~always f = | ||
match f () with | ||
| x -> | ||
always (); | ||
x | ||
| exception e -> | ||
always (); | ||
raise e | ||
|
||
let use_output command = | ||
let fn = Filename.temp_file "ocaml" "_toploop.ml" in | ||
try_finally | ||
~always:(fun () -> try Sys.remove fn with Sys_error _ -> ()) | ||
(fun () -> | ||
match | ||
Printf.ksprintf Sys.command "%s > %s" command (Filename.quote fn) | ||
with | ||
| 0 -> ignore (Toploop.use_file Format.std_formatter fn : bool) | ||
| n -> Format.printf "Command exited with code %d.@." n) | ||
|
||
let () = | ||
let name = "use_output" in | ||
if not (Hashtbl.mem Toploop.directive_table name) then | ||
Hashtbl.add Toploop.directive_table name | ||
(Toploop.Directive_string use_output) |
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.
Do we actually need
topfind
for this? At the moment we only generate#directory
andload
directives and these are available by default.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.
My bad, I thought the
Toplevel
module comes fromtopfind
, but apparently it's available by default. I'll update the docs.