From ac372ce63ab42b78e45244acf69f3913486ec79b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Dimino?= Date: Tue, 28 Feb 2017 06:31:02 +0000 Subject: [PATCH] Allow to use installed libraries in jbuild plugins --- doc/manual.org | 25 ++++++++++++++++++++++++- src/context.ml | 17 +++++++++++++++++ src/context.mli | 3 +++ src/jbuild_load.ml | 38 +++++++++++++++++++++++++++++++++++--- 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/doc/manual.org b/doc/manual.org index 5a223f8c..3dd9fd79 100644 --- a/doc/manual.org +++ b/doc/manual.org @@ -189,11 +189,34 @@ everything Jbuilder needs to know about. *** OCaml syntax 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 have access to a [[../plugin/jbuild_plugin.mli][Jbuild_plugin]] module containing details about the 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 =jbuild= files are composed of stanzas. For instance a typical diff --git a/src/context.ml b/src/context.ml index a61d923f..d4a0c880 100644 --- a/src/context.ml +++ b/src/context.ml @@ -18,6 +18,7 @@ type t = ; for_host : t option ; build_dir : Path.t ; path : Path.t list + ; toplevel_path : Path.t option ; ocaml_bin : Path.t ; ocaml : Path.t ; ocamlc : Path.t @@ -86,6 +87,21 @@ let opam_config_var ~env ~cache var = Hashtbl.add cache ~key:var ~data: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 opam_var_cache = Hashtbl.create 128 in (match kind with @@ -183,6 +199,7 @@ let create ~(kind : Kind.t) ~path ~env ~name ~merlin = ; for_host = None ; build_dir ; path + ; toplevel_path = Option.map (get_env env "OCAML_TOPLEVEL_PATH") ~f:Path.of_string ; ocaml_bin = dir ; ocaml = Path.relative dir "ocaml" diff --git a/src/context.mli b/src/context.mli index d6ca9437..06c10bca 100644 --- a/src/context.mli +++ b/src/context.mli @@ -47,6 +47,9 @@ type t = ; (** [PATH] *) path : Path.t list + ; (** [OCAML_TOPLEVEL_PATH] *) + toplevel_path : Path.t option + ; (** Ocaml bin directory with all ocaml tools *) ocaml_bin : Path.t ; ocaml : Path.t diff --git a/src/jbuild_load.ml b/src/jbuild_load.ml index af5e560b..7560d19a 100644 --- a/src/jbuild_load.ml +++ b/src/jbuild_load.ml @@ -34,6 +34,7 @@ module Jbuilds = struct let plugin_contents = read_file plugin in with_file_out (Path.to_string wrapper) ~f:(fun oc -> Printf.fprintf oc {| +let () = Hashtbl.add Toploop.directive_table "require" (Toploop.Directive_string ignore) module Jbuild_plugin = struct module V1 = struct let context = %S @@ -75,12 +76,43 @@ end in let wrapper = Path.extend_basename generated_jbuild ~suffix:".ml" in ensure_parent_dir_exists generated_jbuild; - let _requires = + let requires = create_plugin_wrapper context ~exec_dir:dir ~plugin:file ~wrapper ~target:generated_jbuild 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 - (Path.to_string context.Context.ocaml) - [ Path.reach ~from:dir wrapper ] + (Path.to_string context.ocaml) + args >>= fun () -> let sexps = Sexp_load.many (Path.to_string generated_jbuild) in return (dir, Stanzas.parse sexps ~dir ~visible_packages))