Better for user written/generated META files
This commit is contained in:
parent
a257da3f94
commit
ee7ab05d9e
|
@ -780,3 +780,35 @@ The following constructions are available:
|
||||||
* Usage
|
* Usage
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
|
* Advanced topics
|
||||||
|
|
||||||
|
This section describes some details of Jbuilder for advanced users.
|
||||||
|
|
||||||
|
** META file generation
|
||||||
|
|
||||||
|
Jbuilder uses =META= files from the [[http://projects.camlcity.org/projects/findlib.html][findlib library manager]] in order
|
||||||
|
to inter-operate with the rest of the world when installing
|
||||||
|
libraries. It is able to generate them automatically. However, for the
|
||||||
|
rare cases where you would need a specific =META= file, or to ease the
|
||||||
|
transition of a project to Jbuilder, it is allowed to write/generate a
|
||||||
|
specific one.
|
||||||
|
|
||||||
|
In order to do that, write or setup a rule to generate a
|
||||||
|
=META.<package>= file in the same directory as the =<package>.opam=
|
||||||
|
file. If you do that, Jbuilder will still generate a =META= file but
|
||||||
|
it will be called =META.<package>.from-jbuilder=. So for instance if
|
||||||
|
you want to extend the =META= file generated by Jbuilder you can
|
||||||
|
write:
|
||||||
|
|
||||||
|
#+begin_src scheme
|
||||||
|
(rule
|
||||||
|
((targets (META.foo))
|
||||||
|
(deps (META.foo.from-jbuilder))
|
||||||
|
(action "{ cat ${<}; echo blah } > ${@}")))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Additionally, Jbuilder provides a simpler mechanism for this scheme:
|
||||||
|
just write or generate a =META.<package>.template= file containing a
|
||||||
|
line of the form =# JBUILDER_GEN=. Jbuilder will automatically insert
|
||||||
|
its generated =META= contents in place of this line.
|
||||||
|
|
|
@ -46,7 +46,8 @@ let rules store ~prefix ~tree =
|
||||||
Hashtbl.fold store ~init:[] ~f:(fun ~key:alias ~data:deps acc ->
|
Hashtbl.fold store ~init:[] ~f:(fun ~key:alias ~data:deps acc ->
|
||||||
let open Build.O in
|
let open Build.O in
|
||||||
let rule =
|
let rule =
|
||||||
Build.path_set !deps >>>
|
Build_interpret.Rule.make
|
||||||
Build.touch alias
|
(Build.path_set !deps >>>
|
||||||
|
Build.touch alias)
|
||||||
in
|
in
|
||||||
rule :: acc)
|
rule :: acc)
|
||||||
|
|
|
@ -21,4 +21,4 @@ val rules
|
||||||
: Store.t
|
: Store.t
|
||||||
-> prefix:Path.t
|
-> prefix:Path.t
|
||||||
-> tree:tree
|
-> tree:tree
|
||||||
-> (unit, unit) Build.t list
|
-> Build_interpret.Rule.t list
|
||||||
|
|
|
@ -92,3 +92,15 @@ let targets =
|
||||||
| Fail _ -> acc
|
| Fail _ -> acc
|
||||||
in
|
in
|
||||||
fun t -> loop (Build.repr t) []
|
fun t -> loop (Build.repr t) []
|
||||||
|
|
||||||
|
module Rule = struct
|
||||||
|
type t =
|
||||||
|
{ build : (unit, unit) Build.t
|
||||||
|
; targets : Target.t list
|
||||||
|
}
|
||||||
|
|
||||||
|
let make build =
|
||||||
|
{ build
|
||||||
|
; targets = targets build
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
|
@ -9,6 +9,15 @@ module Target : sig
|
||||||
val paths : t list -> Path.Set.t
|
val paths : t list -> Path.Set.t
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module Rule : sig
|
||||||
|
type t =
|
||||||
|
{ build : (unit, unit) Build.t
|
||||||
|
; targets : Target.t list
|
||||||
|
}
|
||||||
|
|
||||||
|
val make : (unit, unit) Build.t -> t
|
||||||
|
end
|
||||||
|
|
||||||
val deps
|
val deps
|
||||||
: (_, _) Build.t
|
: (_, _) Build.t
|
||||||
-> all_targets_by_dir:Path.Set.t Path.Map.t Lazy.t
|
-> all_targets_by_dir:Path.Set.t Path.Map.t Lazy.t
|
||||||
|
|
|
@ -200,17 +200,7 @@ let create_file_specs t targets rule ~allow_override =
|
||||||
| Target.Vfile (Vspec.T (fn, kind)) ->
|
| Target.Vfile (Vspec.T (fn, kind)) ->
|
||||||
add_spec t fn (File_spec.create rule (Sexp_file kind)) ~allow_override)
|
add_spec t fn (File_spec.create rule (Sexp_file kind)) ~allow_override)
|
||||||
|
|
||||||
module Pre_rule = struct
|
module Pre_rule = Build_interpret.Rule
|
||||||
type t =
|
|
||||||
{ build : (unit, unit) Build.t
|
|
||||||
; targets : Target.t list
|
|
||||||
}
|
|
||||||
|
|
||||||
let make build =
|
|
||||||
{ build
|
|
||||||
; targets = Build_interpret.targets build
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
let compile_rule t ~all_targets_by_dir ?(allow_override=false) pre_rule =
|
let compile_rule t ~all_targets_by_dir ?(allow_override=false) pre_rule =
|
||||||
let { Pre_rule. build; targets = target_specs } = pre_rule in
|
let { Pre_rule. build; targets = target_specs } = pre_rule in
|
||||||
|
@ -276,7 +266,6 @@ let setup_copy_rules t ~all_non_target_source_files ~all_targets_by_dir =
|
||||||
~allow_override:true))
|
~allow_override:true))
|
||||||
|
|
||||||
let create ~file_tree ~rules =
|
let create ~file_tree ~rules =
|
||||||
let rules = List.map rules ~f:Pre_rule.make in
|
|
||||||
let all_source_files =
|
let all_source_files =
|
||||||
File_tree.fold file_tree ~init:Pset.empty ~f:(fun dir acc ->
|
File_tree.fold file_tree ~init:Pset.empty ~f:(fun dir acc ->
|
||||||
let path = File_tree.Dir.path dir in
|
let path = File_tree.Dir.path dir in
|
||||||
|
|
|
@ -4,7 +4,7 @@ open! Import
|
||||||
|
|
||||||
type t
|
type t
|
||||||
|
|
||||||
val create : file_tree:File_tree.t -> rules:(unit, unit) Build.t list -> t
|
val create : file_tree:File_tree.t -> rules:Build_interpret.Rule.t list -> t
|
||||||
|
|
||||||
val is_target : t -> Path.t -> bool
|
val is_target : t -> Path.t -> bool
|
||||||
|
|
||||||
|
|
221
src/gen_rules.ml
221
src/gen_rules.ml
|
@ -244,8 +244,34 @@ module Gen(P : Params) = struct
|
||||||
let rules () = rules store ~prefix:ctx.build_dir ~tree:P.tree
|
let rules () = rules store ~prefix:ctx.build_dir ~tree:P.tree
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
let all_rules = ref []
|
let all_rules = ref []
|
||||||
let add_rule rule = all_rules := rule :: !all_rules
|
let known_targets_by_dir_so_far = ref Path.Map.empty
|
||||||
|
|
||||||
|
let add_rule build =
|
||||||
|
let rule = Build_interpret.Rule.make build in
|
||||||
|
all_rules := rule :: !all_rules;
|
||||||
|
known_targets_by_dir_so_far :=
|
||||||
|
List.fold_left rule.targets ~init:!known_targets_by_dir_so_far ~f:(fun acc target ->
|
||||||
|
let path = Build_interpret.Target.path target in
|
||||||
|
let dir = Path.parent path in
|
||||||
|
let fn = Path.basename path in
|
||||||
|
let files =
|
||||||
|
match Path.Map.find dir acc with
|
||||||
|
| None -> String_set.singleton fn
|
||||||
|
| Some set -> String_set.add fn set
|
||||||
|
in
|
||||||
|
Path.Map.add acc ~key:dir ~data:files)
|
||||||
|
|
||||||
|
let sources_and_targets_known_so_far ~src_path =
|
||||||
|
let sources =
|
||||||
|
match File_tree.find_dir P.file_tree src_path with
|
||||||
|
| None -> String_set.empty
|
||||||
|
| Some dir -> File_tree.Dir.files dir
|
||||||
|
in
|
||||||
|
match Path.Map.find src_path !known_targets_by_dir_so_far with
|
||||||
|
| None -> sources
|
||||||
|
| Some set -> String_set.union sources set
|
||||||
|
|
||||||
(* +-----------------------------------------------------------------+
|
(* +-----------------------------------------------------------------+
|
||||||
| Tools |
|
| Tools |
|
||||||
|
@ -1350,76 +1376,6 @@ module Gen(P : Params) = struct
|
||||||
; obj_name = ""
|
; obj_name = ""
|
||||||
})
|
})
|
||||||
|
|
||||||
(* +-----------------------------------------------------------------+
|
|
||||||
| META |
|
|
||||||
+-----------------------------------------------------------------+ *)
|
|
||||||
|
|
||||||
let stanzas_to_consider_for_install =
|
|
||||||
if P.filter_out_optional_stanzas_with_missing_deps then
|
|
||||||
List.concat_map P.stanzas ~f:(fun { ctx_dir; stanzas; _ } ->
|
|
||||||
List.filter_map stanzas ~f:(function
|
|
||||||
| Library _ -> None
|
|
||||||
| stanza -> Some (ctx_dir, stanza)))
|
|
||||||
@ List.map (Lib_db.internal_libs_without_non_installable_optional_ones)
|
|
||||||
~f:(fun (dir, lib) -> (dir, Stanza.Library lib))
|
|
||||||
else
|
|
||||||
List.concat_map P.stanzas ~f:(fun { ctx_dir; stanzas; _ } ->
|
|
||||||
List.map stanzas ~f:(fun s -> (ctx_dir, s)))
|
|
||||||
|
|
||||||
let () =
|
|
||||||
String_map.iter P.packages ~f:(fun ~key:package ~data:src_path ->
|
|
||||||
let path = Path.append ctx.build_dir src_path in
|
|
||||||
let meta_fn = "META." ^ package in
|
|
||||||
let meta_path = Path.relative path meta_fn in
|
|
||||||
let template =
|
|
||||||
let templ_fn = meta_fn ^ ".template" in
|
|
||||||
let templ_path = Path.relative src_path templ_fn in
|
|
||||||
if File_tree.exists P.file_tree templ_path then
|
|
||||||
Build.path templ_path
|
|
||||||
>>^ fun () ->
|
|
||||||
lines_of_file (Path.to_string templ_path)
|
|
||||||
else
|
|
||||||
Build.return ["# JBUILDER_GEN"]
|
|
||||||
in
|
|
||||||
let meta =
|
|
||||||
Gen_meta.gen ~package
|
|
||||||
~stanzas:stanzas_to_consider_for_install
|
|
||||||
~lib_deps:(fun ~dir jbuild ->
|
|
||||||
match jbuild with
|
|
||||||
| Library lib ->
|
|
||||||
Lib_db.load_requires ~dir ~item:lib.name
|
|
||||||
>>^ List.map ~f:Lib.best_name
|
|
||||||
| Executables exes ->
|
|
||||||
let item = List.hd exes.names in
|
|
||||||
Lib_db.load_requires ~dir ~item
|
|
||||||
>>^ List.map ~f:Lib.best_name
|
|
||||||
| _ -> Build.return [])
|
|
||||||
~ppx_runtime_deps:(fun ~dir jbuild ->
|
|
||||||
match jbuild with
|
|
||||||
| Library lib ->
|
|
||||||
Lib_db.load_runtime_deps ~dir ~item:lib.name
|
|
||||||
>>^ List.map ~f:Lib.best_name
|
|
||||||
| _ -> Build.return [])
|
|
||||||
in
|
|
||||||
add_rule
|
|
||||||
(Build.fanout meta template
|
|
||||||
>>>
|
|
||||||
Build.create_file ~target:meta_path (fun ((meta : Meta.t), template) ->
|
|
||||||
with_file_out (Path.to_string meta_path) ~f:(fun oc ->
|
|
||||||
let ppf = Format.formatter_of_out_channel oc in
|
|
||||||
Format.pp_open_vbox ppf 0;
|
|
||||||
List.iter template ~f:(fun s ->
|
|
||||||
if String.is_prefix s ~prefix:"#" then
|
|
||||||
match
|
|
||||||
String.split_words (String.sub s ~pos:1 ~len:(String.length s - 1))
|
|
||||||
with
|
|
||||||
| ["JBUILDER_GEN"] -> Format.fprintf ppf "%a@," Meta.pp meta.entries
|
|
||||||
| _ -> Format.fprintf ppf "%s@," s
|
|
||||||
else
|
|
||||||
Format.fprintf ppf "%s@," s);
|
|
||||||
Format.pp_close_box ppf ();
|
|
||||||
Format.pp_print_flush ppf ()))))
|
|
||||||
|
|
||||||
(* +-----------------------------------------------------------------+
|
(* +-----------------------------------------------------------------+
|
||||||
| Stanza |
|
| Stanza |
|
||||||
+-----------------------------------------------------------------+ *)
|
+-----------------------------------------------------------------+ *)
|
||||||
|
@ -1465,21 +1421,104 @@ module Gen(P : Params) = struct
|
||||||
let () = List.iter P.stanzas ~f:rules
|
let () = List.iter P.stanzas ~f:rules
|
||||||
|
|
||||||
(* +-----------------------------------------------------------------+
|
(* +-----------------------------------------------------------------+
|
||||||
| Installation |
|
| META |
|
||||||
+-----------------------------------------------------------------+ *)
|
+-----------------------------------------------------------------+ *)
|
||||||
|
|
||||||
let known_targets_by_dir_so_far =
|
(* The rules for META files must come after the interpretation of the jbuild stanzas
|
||||||
List.fold_left !all_rules ~init:Path.Map.empty ~f:(fun acc rule ->
|
since a user rule might generate a META.<package> file *)
|
||||||
List.fold_left (Build_interpret.targets rule) ~init:acc ~f:(fun acc target ->
|
|
||||||
let path = Build_interpret.Target.path target in
|
let stanzas_to_consider_for_install =
|
||||||
let dir = Path.parent path in
|
if P.filter_out_optional_stanzas_with_missing_deps then
|
||||||
let fn = Path.basename path in
|
List.concat_map P.stanzas ~f:(fun { ctx_dir; stanzas; _ } ->
|
||||||
let files =
|
List.filter_map stanzas ~f:(function
|
||||||
match Path.Map.find dir acc with
|
| Library _ -> None
|
||||||
| None -> String_set.singleton fn
|
| stanza -> Some (ctx_dir, stanza)))
|
||||||
| Some set -> String_set.add fn set
|
@ List.map (Lib_db.internal_libs_without_non_installable_optional_ones)
|
||||||
in
|
~f:(fun (dir, lib) -> (dir, Stanza.Library lib))
|
||||||
Path.Map.add acc ~key:dir ~data:files))
|
else
|
||||||
|
List.concat_map P.stanzas ~f:(fun { ctx_dir; stanzas; _ } ->
|
||||||
|
List.map stanzas ~f:(fun s -> (ctx_dir, s)))
|
||||||
|
|
||||||
|
(* META files that must be installed. Either because there is an explicit or user
|
||||||
|
generated one, or because *)
|
||||||
|
let packages_with_explicit_or_user_generated_meta =
|
||||||
|
String_map.bindings P.packages
|
||||||
|
|> List.filter_map ~f:(fun (package, src_path) ->
|
||||||
|
let path = Path.append ctx.build_dir src_path in
|
||||||
|
let meta_fn = "META." ^ package in
|
||||||
|
let meta_templ_fn = meta_fn ^ ".template" in
|
||||||
|
|
||||||
|
let has_meta, has_meta_tmpl =
|
||||||
|
let files = sources_and_targets_known_so_far ~src_path in
|
||||||
|
(String_set.mem meta_fn files,
|
||||||
|
String_set.mem meta_templ_fn files)
|
||||||
|
in
|
||||||
|
|
||||||
|
let meta_fn =
|
||||||
|
if has_meta then
|
||||||
|
meta_fn ^ ".from-jbuilder"
|
||||||
|
else
|
||||||
|
meta_fn
|
||||||
|
in
|
||||||
|
let meta_path = Path.relative path meta_fn in
|
||||||
|
|
||||||
|
let template =
|
||||||
|
if has_meta_tmpl then
|
||||||
|
let meta_templ_path = Path.relative src_path meta_templ_fn in
|
||||||
|
Build.path meta_templ_path
|
||||||
|
>>^ fun () ->
|
||||||
|
lines_of_file (Path.to_string meta_templ_path)
|
||||||
|
else
|
||||||
|
Build.return ["# JBUILDER_GEN"]
|
||||||
|
in
|
||||||
|
let meta =
|
||||||
|
Gen_meta.gen ~package
|
||||||
|
~stanzas:stanzas_to_consider_for_install
|
||||||
|
~lib_deps:(fun ~dir jbuild ->
|
||||||
|
match jbuild with
|
||||||
|
| Library lib ->
|
||||||
|
Lib_db.load_requires ~dir ~item:lib.name
|
||||||
|
>>^ List.map ~f:Lib.best_name
|
||||||
|
| Executables exes ->
|
||||||
|
let item = List.hd exes.names in
|
||||||
|
Lib_db.load_requires ~dir ~item
|
||||||
|
>>^ List.map ~f:Lib.best_name
|
||||||
|
| _ -> Build.return [])
|
||||||
|
~ppx_runtime_deps:(fun ~dir jbuild ->
|
||||||
|
match jbuild with
|
||||||
|
| Library lib ->
|
||||||
|
Lib_db.load_runtime_deps ~dir ~item:lib.name
|
||||||
|
>>^ List.map ~f:Lib.best_name
|
||||||
|
| _ -> Build.return [])
|
||||||
|
in
|
||||||
|
add_rule
|
||||||
|
(Build.fanout meta template
|
||||||
|
>>>
|
||||||
|
Build.create_file ~target:meta_path (fun ((meta : Meta.t), template) ->
|
||||||
|
with_file_out (Path.to_string meta_path) ~f:(fun oc ->
|
||||||
|
let ppf = Format.formatter_of_out_channel oc in
|
||||||
|
Format.pp_open_vbox ppf 0;
|
||||||
|
List.iter template ~f:(fun s ->
|
||||||
|
if String.is_prefix s ~prefix:"#" then
|
||||||
|
match
|
||||||
|
String.split_words (String.sub s ~pos:1 ~len:(String.length s - 1))
|
||||||
|
with
|
||||||
|
| ["JBUILDER_GEN"] -> Format.fprintf ppf "%a@," Meta.pp meta.entries
|
||||||
|
| _ -> Format.fprintf ppf "%s@," s
|
||||||
|
else
|
||||||
|
Format.fprintf ppf "%s@," s);
|
||||||
|
Format.pp_close_box ppf ();
|
||||||
|
Format.pp_print_flush ppf ())));
|
||||||
|
|
||||||
|
if has_meta || has_meta_tmpl then
|
||||||
|
Some package
|
||||||
|
else
|
||||||
|
None)
|
||||||
|
|> String_set.of_list
|
||||||
|
|
||||||
|
(* +-----------------------------------------------------------------+
|
||||||
|
| Installation |
|
||||||
|
+-----------------------------------------------------------------+ *)
|
||||||
|
|
||||||
let lib_install_files ~dir (lib : Library.t) =
|
let lib_install_files ~dir (lib : Library.t) =
|
||||||
let byte = List.mem Mode.Byte ~set:lib.modes in
|
let byte = List.mem Mode.Byte ~set:lib.modes in
|
||||||
|
@ -1561,13 +1600,8 @@ module Gen(P : Params) = struct
|
||||||
| _ -> [])
|
| _ -> [])
|
||||||
in
|
in
|
||||||
let entries =
|
let entries =
|
||||||
let root_listing = File_tree.Dir.files (File_tree.root P.file_tree) in
|
let files = sources_and_targets_known_so_far ~src_path:Path.root in
|
||||||
let root_targets =
|
String_set.fold files ~init:entries ~f:(fun fn acc ->
|
||||||
match Path.Map.find ctx.build_dir known_targets_by_dir_so_far with
|
|
||||||
| None -> root_listing
|
|
||||||
| Some set -> String_set.union root_listing set
|
|
||||||
in
|
|
||||||
String_set.fold root_targets ~init:entries ~f:(fun fn acc ->
|
|
||||||
if is_odig_doc_file fn then
|
if is_odig_doc_file fn then
|
||||||
Install.Entry.make Doc (Path.relative ctx.build_dir fn) :: acc
|
Install.Entry.make Doc (Path.relative ctx.build_dir fn) :: acc
|
||||||
else
|
else
|
||||||
|
@ -1578,9 +1612,10 @@ module Gen(P : Params) = struct
|
||||||
Install.Entry.make Lib opam ~dst:"opam" :: entries
|
Install.Entry.make Lib opam ~dst:"opam" :: entries
|
||||||
in
|
in
|
||||||
let entries =
|
let entries =
|
||||||
|
(* Install a META file if the user wrote one or setup a rule to generate one, or if
|
||||||
|
we have at least another file to install in the lib/ directory *)
|
||||||
let meta_fn = "META." ^ package in
|
let meta_fn = "META." ^ package in
|
||||||
if File_tree.file_exists P.file_tree package_path meta_fn ||
|
if String_set.mem package packages_with_explicit_or_user_generated_meta ||
|
||||||
File_tree.file_exists P.file_tree package_path (meta_fn ^ ".template") ||
|
|
||||||
List.exists entries ~f:(fun (e : Install.Entry.t) -> e.section = Lib) then
|
List.exists entries ~f:(fun (e : Install.Entry.t) -> e.section = Lib) then
|
||||||
let meta = Path.append ctx.build_dir (Path.relative package_path meta_fn) in
|
let meta = Path.append ctx.build_dir (Path.relative package_path meta_fn) in
|
||||||
Install.Entry.make Lib meta ~dst:"META" :: entries
|
Install.Entry.make Lib meta ~dst:"META" :: entries
|
||||||
|
|
|
@ -8,4 +8,4 @@ val gen
|
||||||
-> packages:Path.t String_map.t
|
-> packages:Path.t String_map.t
|
||||||
-> ?filter_out_optional_stanzas_with_missing_deps:bool (** default: true *)
|
-> ?filter_out_optional_stanzas_with_missing_deps:bool (** default: true *)
|
||||||
-> unit
|
-> unit
|
||||||
-> (unit, unit) Build.t list
|
-> Build_interpret.Rule.t list
|
||||||
|
|
Loading…
Reference in New Issue