Allow to use installed libraries in jbuild plugins

This commit is contained in:
Jérémie Dimino 2017-02-28 06:31:02 +00:00
parent 0462d4a11f
commit ac372ce63a
4 changed files with 79 additions and 4 deletions

View File

@ -189,11 +189,34 @@ everything Jbuilder needs to know about.
*** OCaml syntax *** OCaml syntax
If a =jbuild= file starts with =(* -*- tuareg -*- *)=, then it is If a =jbuild= file starts with =(* -*- tuareg -*- *)=, then it is
interpreted as an OCaml script that generated the =jbuild= file as interpreted as an OCaml script that generates the =jbuild= file as
described in the rest of this section. The code in the script will described in the rest of this section. The code in the script will
have access to a [[../plugin/jbuild_plugin.mli][Jbuild_plugin]] module containing details about the have access to a [[../plugin/jbuild_plugin.mli][Jbuild_plugin]] module containing details about the
build context it is executed in. build context it is executed in.
The script can use the directive =#require= to access libraries:
#+begin_src ocaml
#require "base,re";;
#+end_src
Note that any library required by a =jbuild= file must be part of the
installed world. This mean that your project won't be compilable in
the same workspace as the required library, unless the library has
been previously installed.
If you don't like the S-expression syntax, then this method gives you
a way to use whatever else you want. For instance you could have an
API to describe your project in OCaml directly:
#+begin_src ocaml
(* -*- tuareg -*- *)
#require "my_jbuild_api";;
include My_jbuild_api.Make(Jbuild_plugin);;
library "foo" ~modules:["plop"; "bidule"];;
#+end_src
*** Specification *** Specification
=jbuild= files are composed of stanzas. For instance a typical =jbuild= files are composed of stanzas. For instance a typical

View File

@ -18,6 +18,7 @@ type t =
; for_host : t option ; for_host : t option
; build_dir : Path.t ; build_dir : Path.t
; path : Path.t list ; path : Path.t list
; toplevel_path : Path.t option
; ocaml_bin : Path.t ; ocaml_bin : Path.t
; ocaml : Path.t ; ocaml : Path.t
; ocamlc : Path.t ; ocamlc : Path.t
@ -86,6 +87,21 @@ let opam_config_var ~env ~cache var =
Hashtbl.add cache ~key:var ~data:s; Hashtbl.add cache ~key:var ~data:s;
Some s Some s
let get_env env var =
let prefix = var ^ "=" in
let rec loop i =
if i = Array.length env then
None
else
let entry = env.(i) in
if String.is_prefix entry ~prefix then
let len_p = String.length prefix in
Some (String.sub entry ~pos:len_p ~len:(String.length entry - len_p))
else
loop (i + 1)
in
loop 0
let create ~(kind : Kind.t) ~path ~env ~name ~merlin = let create ~(kind : Kind.t) ~path ~env ~name ~merlin =
let opam_var_cache = Hashtbl.create 128 in let opam_var_cache = Hashtbl.create 128 in
(match kind with (match kind with
@ -183,6 +199,7 @@ let create ~(kind : Kind.t) ~path ~env ~name ~merlin =
; for_host = None ; for_host = None
; build_dir ; build_dir
; path ; path
; toplevel_path = Option.map (get_env env "OCAML_TOPLEVEL_PATH") ~f:Path.of_string
; ocaml_bin = dir ; ocaml_bin = dir
; ocaml = Path.relative dir "ocaml" ; ocaml = Path.relative dir "ocaml"

View File

@ -47,6 +47,9 @@ type t =
; (** [PATH] *) ; (** [PATH] *)
path : Path.t list path : Path.t list
; (** [OCAML_TOPLEVEL_PATH] *)
toplevel_path : Path.t option
; (** Ocaml bin directory with all ocaml tools *) ; (** Ocaml bin directory with all ocaml tools *)
ocaml_bin : Path.t ocaml_bin : Path.t
; ocaml : Path.t ; ocaml : Path.t

View File

@ -34,6 +34,7 @@ module Jbuilds = struct
let plugin_contents = read_file plugin in let plugin_contents = read_file plugin in
with_file_out (Path.to_string wrapper) ~f:(fun oc -> with_file_out (Path.to_string wrapper) ~f:(fun oc ->
Printf.fprintf oc {| Printf.fprintf oc {|
let () = Hashtbl.add Toploop.directive_table "require" (Toploop.Directive_string ignore)
module Jbuild_plugin = struct module Jbuild_plugin = struct
module V1 = struct module V1 = struct
let context = %S let context = %S
@ -75,12 +76,43 @@ end
in in
let wrapper = Path.extend_basename generated_jbuild ~suffix:".ml" in let wrapper = Path.extend_basename generated_jbuild ~suffix:".ml" in
ensure_parent_dir_exists generated_jbuild; ensure_parent_dir_exists generated_jbuild;
let _requires = let requires =
create_plugin_wrapper context ~exec_dir:dir ~plugin:file ~wrapper ~target:generated_jbuild create_plugin_wrapper context ~exec_dir:dir ~plugin:file ~wrapper ~target:generated_jbuild
in in
let pkgs =
List.map requires ~f:(Findlib.find_exn context.findlib)
|> Findlib.closure
in
let includes =
List.fold_left pkgs ~init:Path.Set.empty ~f:(fun acc pkg ->
Path.Set.add pkg.Findlib.dir acc)
|> Path.Set.elements
|> List.concat_map ~f:(fun path ->
[ "-I"; Path.to_string path ])
in
let cmas =
List.concat_map pkgs ~f:(fun pkg -> pkg.archives.byte)
in
let args =
List.concat
[ [ "-I"; "+compiler-libs" ]
; includes
; cmas
; [ Path.reach ~from:dir wrapper ]
]
in
(* CR-someday jdimino: if we want to allow plugins to use findlib:
{[
let args =
match context.toplevel_path with
| None -> args
| Some path -> "-I" :: Path.reach ~from:dir path :: args
in
]}
*)
Future.run Strict ~dir:(Path.to_string dir) ~env:context.env Future.run Strict ~dir:(Path.to_string dir) ~env:context.env
(Path.to_string context.Context.ocaml) (Path.to_string context.ocaml)
[ Path.reach ~from:dir wrapper ] args
>>= fun () -> >>= fun () ->
let sexps = Sexp_load.many (Path.to_string generated_jbuild) in let sexps = Sexp_load.many (Path.to_string generated_jbuild) in
return (dir, Stanzas.parse sexps ~dir ~visible_packages)) return (dir, Stanzas.parse sexps ~dir ~visible_packages))