Skip to content

Commit

Permalink
[wip] [coq] Allow dependencies and composition with local Coq libraries.
Browse files Browse the repository at this point in the history
We support simple composition of Coq libraries using the
 `(theories ...)` field.

This is a work-in-progress and I am submitting to gather feedback; in
particular:

- Attention: I ignore what is the right way to handle paths for
  depending libraries. I am using `Path.drop_build_context` however
  that doesn't feel very clear.

- Todo Before Merge:
  + tests
  + Error handling.
  + validation of library names must be adapted to Coq's conventions.

- Would be nice to have / next PRs:
  + Closure of theory dependencies.
  + Allow to depend on libraries installed globally.

Signed-off-by: Emilio Jesus Gallego Arias <e+git@x80.org>
  • Loading branch information
ejgallego committed Apr 12, 2019
1 parent d98e5a4 commit 14e8916
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 38 deletions.
21 changes: 17 additions & 4 deletions doc/coq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Dune is also able to build Coq developments. A Coq project is a mix of
Coq ``.v`` files and (optionally) OCaml libraries linking to the Coq
API (in which case we say the project is a *Coq plugin*). To enable
Coq support in a dune project, the language version should be selected
in the ``dune-project`` file. For example:
in the ``dune-project`` file. As of today, the only version supported
is ``0.1`` and is experimental:

.. code:: scheme
Expand All @@ -32,11 +33,12 @@ The basic form for defining Coq libraries is very similar to the OCaml form:
(synopsis <text>)
(modules <ordered_set_lang>)
(libraries <ocaml_libraries>)
(theories <coq_libraries>)
(flags <coq_flags>))
The stanza will build all `.v` files on the given directory. The semantics of fields is:

- ``<module_prefix>>`` will be used as the default Coq library prefix ``-R``,
- ``<module_prefix>`` will be used as the default Coq library prefix ``-R``,
- the ``modules`` field does allow to constraint the set of modules
included in the library, similarly to its OCaml counterpart,
- ``public_name`` will make Dune generate install rules for the `.vo`
Expand All @@ -47,7 +49,10 @@ The stanza will build all `.v` files on the given directory. The semantics of fi
- ``<coq_flags>`` will be passed to ``coqc``,
- the path to installed locations of ``<ocaml_libraries>`` will be passed to
``coqdep`` and ``coqc`` using Coq's ``-I`` flag; this allows for a Coq
library to depend on a ML plugin.
library to depend on a ML plugin,
- you can depend on in-tree ``<coq_libraries>``, in this case, Dune
will pass to Coq the the corresponding `-Q` flag for the libraries
and their dependencies.

Recursive Qualification of Modules
==================================
Expand All @@ -62,9 +67,17 @@ to a ``dune`` file, Dune will to consider that all the modules in
their directory and sub-directories, adding a prefix to the module
name in the usual Coq style for sub-directories. For example, file ``A/b/C.v`` will be module ``A.b.C``.

Composition of Coq libraries
============================

For local Coq libraries, composition is possible using the
``theories`` field. Libraries are wrapped, however for their own
compilation the scope is opened so qualification is not necessary.

TODO: "rules about prefix shadowing"

Limitations
===========

- composition and scoping of Coq libraries is still not possible. For now, libraries are located using Coq's built-in library management,
- .v always depend on the native version of a plugin,
- a ``foo.mlpack`` file must the present for locally defined plugins to work, this is a limitation of coqdep,
48 changes: 48 additions & 0 deletions src/coq_lib.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
(* This file is licensed under The MIT License *)
(* (c) MINES ParisTech 2018-2019 *)
(* Written by: Emilio Jesús Gallego Arias *)

open! Stdune

type t =
{ name : Lib_name.t
; src_dir : Path.t
; obj_dir : Path.t
; package : Package.Name.t option
}

let name l = l.name
let src_dir l = l.src_dir
let obj_dir l = l.obj_dir
let package l = l.package

module DB = struct

type lib = t
type t = lib Lib_name.Map.t

let create_from_stanza (dir, s : Path.t * Dune_file.Coq.t) =
let name = Dune_file.Coq.best_name s in
name,
{ name
; obj_dir = dir
; src_dir = dir
; package = Option.map s.public ~f:(fun p -> p.package.name)
}

(* XXX: Error handling: register errors and printers *)
let create_from_coqlib_stanzas sl =
match Lib_name.Map.of_list_map ~f:create_from_stanza sl with
| Ok m -> m
| Error (name, _w1, w2) ->
let loc = (snd w2).loc in
raise (Errors.exnf loc "Duplicate theory name: %a" Lib_name.pp name)

let resolve db (loc,name) =
match Lib_name.Map.find db name with
| None ->
Error (Errors.exnf loc "Theory %a not found" Lib_name.pp name)
| Some s -> Ok s


end
27 changes: 27 additions & 0 deletions src/coq_lib.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
(* This file is licensed under The MIT License *)
(* (c) MINES ParisTech 2018-2019 *)
(* Written by: Emilio Jesús Gallego Arias *)

open! Stdune

type t

val name : t -> Lib_name.t

val src_dir : t -> Path.t
val obj_dir : t -> Path.t

val package : t -> Package.Name.t option

module DB : sig

type lib = t
type t

val create_from_coqlib_stanzas
: (Path.t * Dune_file.Coq.t) list
-> t

val resolve : t -> Loc.t * Lib_name.t -> lib Or_exn.t

end
83 changes: 54 additions & 29 deletions src/coq_rules.ml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type coq_context =
; coqpp : Action.program
}

let parse_coqdep ~coq_module (lines : string list) =
let parse_coqdep ~src_dir ~coq_module (lines : string list) =
if coq_debug then Format.eprintf "Parsing coqdep @\n%!";
let source = Coq_module.source coq_module in
let invalid p =
Expand All @@ -45,8 +45,8 @@ let parse_coqdep ~coq_module (lines : string list) =
| Some (basename,deps) ->
let ff = List.hd @@ String.extract_blank_separated_words basename in
let depname, _ = Filename.split_extension ff in
let modname =
Coq_module.(String.concat ~sep:"/" (prefix coq_module @ [name coq_module])) in
let modname = String.concat ~sep:"/" Coq_module.(prefix coq_module @ [name coq_module]) in
let modname = Path.(to_string (relative src_dir modname)) in
if coq_debug
then Format.eprintf "depname / modname: %s / %s@\n%!" depname modname;
if depname <> modname then invalid "basename";
Expand All @@ -55,8 +55,13 @@ let parse_coqdep ~coq_module (lines : string list) =
then Format.eprintf "deps for %a: %a@\n%!" Path.pp source Fmt.(list text) deps;
deps

let setup_rule ~expander ~dir ~cc ~source_rule ~wrapper_name
~coq_flags ~ml_iflags ~mlpack_rule coq_module =
let setup_theory_flag lib =
let wrapper = Lib_name.to_string (Coq_lib.name lib) in
let dir = Path.drop_build_context_exn (Coq_lib.src_dir lib) in
["-Q"; Path.to_string dir; wrapper]

let setup_rule ~expander ~base_dir ~dir ~cc ~source_rule ~wrapper_name
~coq_flags ~theories_libs ~ml_iflags ~mlpack_rule coq_module =

if coq_debug
then Format.eprintf "gen_rule coq_module: %a@\n%!" Coq_module.pp coq_module;
Expand All @@ -66,7 +71,9 @@ let setup_rule ~expander ~dir ~cc ~source_rule ~wrapper_name
let stdout_to = Coq_module.obj_file ~obj_dir ~ext:".v.d" coq_module in
let object_to = Coq_module.obj_file ~obj_dir ~ext:".vo" coq_module in

let iflags = Arg_spec.As ["-R"; "."; wrapper_name] in
let src_dir = Path.drop_build_context_exn dir in
let th_flags = List.concat_map theories_libs ~f:setup_theory_flag in
let iflags = Arg_spec.As (["-R"; Path.to_string src_dir; wrapper_name] @ th_flags) in
let cd_arg : (string list, _) Arg_spec.t list =
Arg_spec.[ As ["-dyndep"; "opt"]; iflags; ml_iflags; Dep source ] in

Expand All @@ -75,14 +82,14 @@ let setup_rule ~expander ~dir ~cc ~source_rule ~wrapper_name
(* This is weird stuff in order to adapt the rule so we can reuse
ml_iflags :( I wish we had more flexible typing. *)
((fun () -> []) ^>> source_rule &&& mlpack_rule) >>^ fst >>>
Build.run ~dir ~stdout_to cc.coqdep cd_arg
Build.run ~dir:base_dir ~stdout_to cc.coqdep cd_arg
in

(* Process coqdep and generate rules *)
let deps_of = Build.dyn_paths (
Build.lines_of stdout_to >>^
parse_coqdep ~coq_module >>^
List.map ~f:(Path.relative dir)
parse_coqdep ~src_dir ~coq_module >>^
List.map ~f:(Path.relative base_dir)
) in

let cc_arg = Arg_spec.[
Expand All @@ -96,7 +103,7 @@ let setup_rule ~expander ~dir ~cc ~source_rule ~wrapper_name
[coqdep_rule;
deps_of >>>
Expander.expand_and_eval_set expander coq_flags ~standard:(Build.return []) >>>
Build.run ~dir cc.coqc (Dyn (fun flags -> As flags) :: cc_arg)
Build.run ~dir:base_dir cc.coqc (Dyn (fun flags -> As flags) :: cc_arg)
]

(* TODO: remove; rgrinberg points out:
Expand All @@ -112,14 +119,16 @@ let create_ccoq sctx ~dir =
}

(* get_libraries from Coq's ML dependencies *)
let libs_of_coq_deps ~loc ~scope libs =
let libs_of_coq_deps ~scope libs =
let lib_db = Scope.libs scope in
List.concat_map ~f:Dune_file.Lib_dep.to_lib_names libs
|> Lib.DB.find_many ~loc lib_db
List.map ~f:(fun lib_dep ->
Dune_file.Lib_dep.get_loc lib_dep,
Dune_file.Lib_dep.to_lib_names lib_dep) libs
|> Result.List.concat_map ~f:(fun (loc, ln) -> Lib.DB.find_many ~loc lib_db ln)
|> Result.ok_exn

(* compute include flags and mlpack rules *)
let setup_ml_deps ~scope ~loc libs =
let setup_ml_deps ~scope libs =

(* coqdep expects an mlpack file next to the sources otherwise it
* will omit the cmxs deps *)
Expand All @@ -134,7 +143,7 @@ let setup_ml_deps ~scope ~loc libs =

(* Pair of include flags and paths to mlpack *)
let ml_iflags, mlpack =
let libs = libs_of_coq_deps ~loc ~scope libs in
let libs = libs_of_coq_deps ~scope libs in
Util.include_flags libs, List.concat_map ~f:ml_pack_files libs
in

Expand All @@ -144,40 +153,56 @@ let setup_ml_deps ~scope ~loc libs =
let coqlib_wrapper_name (s : Dune_file.Coq.t) =
Lib_name.Local.to_string (snd s.name)

let setup_rules ~sctx ~dir ~dir_contents (s : Dune_file.Coq.t) =
let coq_modules_of_dep ~sctx lib =
let name = Coq_lib.name lib in
let dir = Coq_lib.src_dir lib in
let dir_contents = Dir_contents.get_without_rules sctx ~dir in
Dir_contents.coq_modules_of_library dir_contents ~name

let scope = SC.find_scope_by_dir sctx dir in
let coq_theories_of_lib_deps ~lib_db =
List.concat_map ~f:(fun lib_dep ->
let loc = Dune_file.Lib_dep.get_loc lib_dep in
Dune_file.Lib_dep.to_lib_names lib_dep
|> List.map ~f:(fun name -> Result.ok_exn (Coq_lib.DB.resolve lib_db (loc, name))))

if coq_debug then begin
let scope = SC.find_scope_by_dir sctx dir in
Format.eprintf "[gen_rules] @[dir: %a@\nscope: %a@]@\n%!"
Path.pp dir Path.pp (Scope.root scope)
end;
let setup_rules ~sctx ~dir ~dir_contents (s : Dune_file.Coq.t) =

let cc = create_ccoq sctx ~dir in
let scope = SC.find_scope_by_dir sctx dir in
let lib_db = Scope.coq_libs scope in

let name = Dune_file.Coq.best_name s in

let coq_modules = Dir_contents.coq_modules_of_library dir_contents ~name in

let theories_libs = coq_theories_of_lib_deps ~lib_db s.theories in
let theories_modules =
List.concat_map theories_libs ~f:(coq_modules_of_dep ~sctx) in

(* coqdep requires all the files to be in the tree to produce correct
dependencies *)
let source_rule = Build.paths (List.map ~f:Coq_module.source coq_modules) in
dependencies, including those of dependencies *)
let source_rule =
List.rev_map ~f:Coq_module.source theories_modules
|> List.rev_map_append ~f:Coq_module.source coq_modules
|> Build.paths in

let coq_flags = s.flags in
let expander = SC.expander sctx ~dir in
let wrapper_name = coqlib_wrapper_name s in

let ml_iflags, mlpack_rule =
setup_ml_deps ~scope ~loc:s.loc s.libraries in
let ml_iflags, mlpack_rule = setup_ml_deps ~scope s.libraries in
let base_dir = Scope.root scope in

let coq_rules =
List.concat_map
~f:(setup_rule ~expander ~dir ~cc ~source_rule ~wrapper_name ~coq_flags
~ml_iflags ~mlpack_rule) coq_modules in
~f:(setup_rule ~expander ~base_dir ~dir ~cc ~source_rule ~wrapper_name ~coq_flags
~theories_libs ~ml_iflags ~mlpack_rule) coq_modules in
coq_rules

(* This is here for compatibility with Coq < 8.11, which expects
plugin files to be in the folder containing the `.vo` files *)
let coq_plugins_install_rules ~scope ~package ~dst_dir (s : Dune_file.Coq.t) =
let ml_libs = libs_of_coq_deps ~scope ~loc:s.loc s.libraries in
let ml_libs = libs_of_coq_deps ~scope s.libraries in
let rules_for_lib lib =
(* Don't install libraries that don't belong to this package *)
if Option.equal Package.Name.equal
Expand Down
13 changes: 12 additions & 1 deletion src/dune_file.ml
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,11 @@ module Lib_dep = struct
let direct x = Direct x

let of_lib_name (loc, pp) = Direct (loc, pp)

let get_loc = function
| Direct (loc,_) -> loc
| Select s -> s.loc

end

module Lib_deps = struct
Expand Down Expand Up @@ -1823,6 +1828,8 @@ module Coq = struct
; flags : Ordered_set_lang.Unexpanded.t
; libraries : Lib_dep.t list
(** ocaml libraries *)
; theories : Lib_dep.t list
(** coq libraries *)
; loc : Loc.t
; enabled_if : Blang.t
}
Expand All @@ -1842,18 +1849,22 @@ module Coq = struct
and+ flags = field_oslu "flags"
and+ modules = modules_field "modules"
and+ libraries = field "libraries" Lib_deps.decode ~default:[]
and+ theories = field "theories" Lib_deps.decode ~default:[]
and+ enabled_if = enabled_if
in
let name =
let (loc, res) = name in
(loc, Lib_name.Local.validate (loc, res) ~wrapped:None)
match res with
| Ok t | Warn t -> (loc, t)
| Invalid -> Errors.fail loc "%s" Lib_name.Local.invalid_message
in
{ name
; public
; synopsis
; modules
; flags
; libraries
; theories
; loc
; enabled_if
})
Expand Down
3 changes: 3 additions & 0 deletions src/dune_file.mli
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ module Lib_dep : sig
val to_lib_names : t -> Lib_name.t list
val direct : Loc.t * Lib_name.t -> t
val of_lib_name : Loc.t * Lib_name.t -> t
val get_loc : t -> Loc.t
end

module Lib_deps : sig
Expand Down Expand Up @@ -363,6 +364,8 @@ module Coq : sig
; flags : Ordered_set_lang.Unexpanded.t
; libraries : Lib_dep.t list
(** ocaml libraries *)
; theories : Lib_dep.t list
(** coq libraries *)
; loc : Loc.t
; enabled_if : Blang.t
}
Expand Down
Loading

0 comments on commit 14e8916

Please sign in to comment.