From 3ae53de4318314395035b803a35c4ef325f7d92a Mon Sep 17 00:00:00 2001 From: Jeremie Dimino Date: Wed, 22 Mar 2017 15:09:26 +0000 Subject: [PATCH] Add an "executable" stanza and make it easier to install executables (executable ...) is easier to use where there is a single executable. Also add a (package ...) and (public_name )/(public_names ()) +(executable + ((name ) )) #+end_src -== is a list of module names that contain the main -entry point of each executables. There can be additional modules in -the current directory, you only need to list the entry point in -=(names ...)=. For every ==, Jbuilder will know how to build -=.exe= and =.bc=. =.exe= is a native code executable -and =.bc= is a bytecode executable which requires =ocamlrun= to -run. +== is a module name that contain the main entry +point of the executable. There can be additional modules in the +current directory, you only need to specify the entry point. Given an +=executable= stanza with =(name )=, Jbuilder will know how to +build =.exe= and =.bc=. =.exe= is a native code +executable and =.bc= is a bytecode executable which requires +=ocamlrun= to run. Note that in case native compilation is not available, =.exe= will in fact be a custom byte-code executable. Custom in the sense of @@ -417,14 +417,26 @@ can always rely on =.exe= being available. == are: +- =(public_name )= specify that the executable should be + installed under that name. It is the same as adding the following + stanza to your =jbuild= file: + #+begin_src scheme + (install + ((section bin) + (files ((.exe as ))))) + #+end_src + +- =(package )= if there is a =(public_name ...)= field, this + specifies the package the executables are part of + - =(libraries ())= specifies the library dependencies. See the [[Library dependencies][section about library dependencies]] for more details - =(modules )= specifies which modules in the current - directory Jbuilder should consider when building - executables. Modules not listed here will be ignored and cannot be - used inside executables described by the current stanza. It is + directory Jbuilder should consider when building this + executable. Modules not listed here will be ignored and cannot be + used inside the executable described by the current stanza. It is interpreted in the same way as the =(modules ...)= field of [[library][libraries]] @@ -437,6 +449,24 @@ can always rely on =.exe= being available. - =flags=, =ocamlc_flags= and =ocamlopt_flags=. See the [[OCaml flags][section about specifying OCaml flags]] +**** executables + +The =executables= stanza is the same as the =executable= stanza, +except that it is used to describe several executables sharing the +same configuration. + +It shares the same fields as the =executable= stanza, except that +instead of =(name ...)= and =(public_name ...)= you must use: + +- =(names ())= where == is a list of entry point + names. As for =executable= you only need to specify the modules + containing the entry point of each executable + +- =(public_names ())= describes under what name each executable + should be installed. The List of names must be of the same length as + the list in the =(names ...)= field. Moreover you can use =-= for + executables that shouldn't be installed + **** rule The =rule= stanza is used to create custom user rules. It tells diff --git a/src/jbuild_types.ml b/src/jbuild_types.ml index 6d43e266..15894e8d 100644 --- a/src/jbuild_types.ml +++ b/src/jbuild_types.ml @@ -517,6 +517,41 @@ module Library = struct List.map t.virtual_deps ~f:(fun s -> Lib_dep.Direct s) @ t.buildable.libraries end +module Install_conf = struct + type file = + { src : string + ; dst : string option + } + + let file sexp = + match sexp with + | Atom (_, src) -> { src; dst = None } + | List (_, [Atom (_, src); Atom (_, "as"); Atom (_, dst)]) -> + { src; dst = Some dst } + | _ -> + of_sexp_error sexp + "invalid format, or ( as ) expected" + + type t = + { section : Install.Section.t + ; files : file list + ; package : string option + } + + let v1 = + record + (field "section" Install.Section.t >>= fun section -> + field "files" (list file) >>= fun files -> + field_o "package" string >>= fun package -> + return + { section + ; files + ; package + }) + + let vjs = v1 +end + module Executables = struct type t = { names : string list @@ -525,18 +560,55 @@ module Executables = struct ; buildable : Buildable.t } - let v1 = + let common_v1 names public_names = + Buildable.v1 >>= fun buildable -> + field "link_executables" bool ~default:true >>= fun link_executables -> + field "link_flags" (list string) ~default:[] >>= fun link_flags -> + field_o "package" string >>= fun package -> + let t = + { names + ; link_executables + ; link_flags + ; buildable + } + in + let to_install = + List.map2 names public_names + ~f:(fun name pub -> + match pub with + | None -> None + | Some pub -> Some ({ Install_conf. src = name ^ ".exe"; dst = Some pub })) + |> List.filter_map ~f:(fun x -> x) + in + match to_install with + | [] -> return (t, None) + | files -> + return (t, Some { Install_conf. section = Bin; files; package }) + + let public_name sexp = + match string sexp with + | "-" -> None + | s -> Some s + + let v1_multi = record - (Buildable.v1 >>= fun buildable -> - field "names" (list string) >>= fun names -> - field "link_executables" bool ~default:true >>= fun link_executables -> - field "link_flags" (list string) ~default:[] >>= fun link_flags -> - return - { names - ; link_executables - ; link_flags - ; buildable - }) + (field "names" (list string) >>= fun names -> + map_validate (field_o "public_names" (list public_name)) ~f:(function + | None -> Ok (List.map names ~f:(fun _ -> None)) + | Some public_names -> + if List.length public_names = List.length names then + Ok public_names + else + Error "The list of public names must be of the same \ + length as the list of names") + >>= fun public_names -> + common_v1 names public_names) + + let v1_single = + record + (field "name" string >>= fun name -> + field_o "public_name" string >>= fun public_name -> + common_v1 [name] [public_name]) let vjs = record @@ -631,41 +703,6 @@ module Provides = struct let vjs = v1 end -module Install_conf = struct - type file = - { src : string - ; dst : string option - } - - let file sexp = - match sexp with - | Atom (_, src) -> { src; dst = None } - | List (_, [Atom (_, src); Atom (_, "as"); Atom (_, dst)]) -> - { src; dst = Some dst } - | _ -> - of_sexp_error sexp - "invalid format, or ( as ) expected" - - type t = - { section : Install.Section.t - ; files : file list - ; package : string option - } - - let v1 = - record - (field "section" Install.Section.t >>= fun section -> - field "files" (list file) >>= fun files -> - field_o "package" string >>= fun package -> - return - { section - ; files - ; package - }) - - let vjs = v1 -end - module Alias_conf = struct type t = { name : string @@ -702,10 +739,16 @@ module Stanza = struct let rules l = List.map l ~f:(fun x -> Rule x) + let execs (exe, install) = + match install with + | None -> [Executables exe] + | Some i -> [Executables exe; Install i] + let v1 = sum [ cstr "library" (Library.v1 @> nil) (fun x -> [Library x]) - ; cstr "executables" (Executables.v1 @> nil) (fun x -> [Executables x]) + ; cstr "executable" (Executables.v1_single @> nil) execs + ; cstr "executables" (Executables.v1_multi @> nil) execs ; cstr "rule" (Rule.v1 @> nil) (fun x -> [Rule x]) ; cstr "ocamllex" (list string @> nil) (fun x -> rules (Rule.ocamllex_v1 x)) ; cstr "ocamlyacc" (list string @> nil) (fun x -> rules (Rule.ocamlyacc_v1 x)) diff --git a/src/sexp.ml b/src/sexp.ml index 57dde7e8..3bf97965 100644 --- a/src/sexp.ml +++ b/src/sexp.ml @@ -230,6 +230,30 @@ module Of_sexp = struct ; known = List.rev_append names state.known }) + let map_validate parse ~f state = + let x, state' = parse state in + match f x with + | Ok x -> x, state' + | Error msg -> + let parsed = + Name_map.merge state.unparsed state'.unparsed ~f:(fun _key before after -> + match before, after with + | Some _, None -> before + | _ -> None) + in + let loc = + match + Name_map.values parsed + |> List.map ~f:(fun f -> Ast.loc f.entry) + |> List.sort ~cmp:(fun a b -> compare a.Loc.start.pos_cnum b.start.pos_cnum) + with + | [] -> state.loc + | first :: l -> + let last = List.fold_left l ~init:first ~f:(fun _ x -> x) in + { first with stop = last.stop } + in + Loc.fail loc "%s" msg + let field name ?default value_of_sexp state = match Name_map.find name state.unparsed with | Some { value = Some value; _ } -> diff --git a/src/sexp.mli b/src/sexp.mli index 1e9091fa..479c5344 100644 --- a/src/sexp.mli +++ b/src/sexp.mli @@ -66,6 +66,8 @@ module Of_sexp : sig val field_o : string -> 'a t -> 'a option record_parser val field_b : string -> bool record_parser + val map_validate : 'a record_parser -> f:('a -> ('b, string) result) -> 'b record_parser + val ignore_fields : string list -> unit record_parser val record : 'a record_parser -> 'a t