dune/src/jbuild_types.ml

877 lines
26 KiB
OCaml
Raw Normal View History

2016-12-02 13:54:32 +00:00
open Import
open Sexp.Of_sexp
2017-02-24 09:41:32 +00:00
(* This file defines the jbuild types as well as the S-expression syntax for the various
supported version of the specification.
[vN] is for the version [N] of the specification and [vjs] is for the rolling
[jane_street] version, when needed.
2017-02-24 09:41:32 +00:00
*)
2017-02-26 19:49:54 +00:00
module Jbuild_version = struct
type t =
| V1
| Vjs
let t =
enum
[ "1", V1
; "jane_street", Vjs
]
let latest_stable = V1
end
2016-12-02 13:54:32 +00:00
let invalid_module_name sexp =
2017-02-24 10:49:27 +00:00
of_sexp_error sexp "invalid module name"
2016-12-02 13:54:32 +00:00
let module_name sexp =
match string sexp with
| "" -> invalid_module_name sexp
| s ->
if s.[0] = '_' then invalid_module_name sexp;
String.iter s ~f:(function
| 'A'..'Z' | 'a'..'z' | '_' -> ()
| _ -> invalid_module_name sexp);
String.capitalize_ascii s
2016-12-02 13:54:32 +00:00
let module_names sexp = String_set.of_list (list module_name sexp)
let invalid_lib_name sexp =
2017-02-24 10:49:27 +00:00
of_sexp_error sexp "invalid library name"
2016-12-02 13:54:32 +00:00
let library_name sexp =
match string sexp with
| "" -> invalid_lib_name sexp
| s ->
if s.[0] = '.' then invalid_lib_name sexp;
String.iter s ~f:(function
| 'A'..'Z' | 'a'..'z' | '_' | '.' | '0'..'9' -> ()
| _ -> invalid_lib_name sexp);
s
let file sexp =
match string sexp with
| "." | ".." ->
2017-02-24 10:49:27 +00:00
of_sexp_error sexp "'.' and '..' are not valid filenames"
2016-12-02 13:54:32 +00:00
| fn -> fn
let file_in_current_dir sexp =
match string sexp with
| "." | ".." ->
2017-02-24 10:49:27 +00:00
of_sexp_error sexp "'.' and '..' are not valid filenames"
2016-12-02 13:54:32 +00:00
| fn ->
if Filename.dirname fn <> Filename.current_dir_name then
2017-02-24 10:49:27 +00:00
of_sexp_error sexp "file in current directory expected";
2016-12-02 13:54:32 +00:00
fn
module Raw_string () : sig
type t = private string
val to_string : t -> string
val of_string : string -> t
2017-02-25 17:53:39 +00:00
val t : t Sexp.Of_sexp.t
2016-12-02 13:54:32 +00:00
end = struct
type t = string
let to_string t = t
let of_string t = t
let t = string
end
module Raw_command = Raw_string ()
module Pp = struct
include Raw_string ()
let of_string s =
assert (not (String.is_prefix s ~prefix:"-"));
of_string s
let t sexp =
let s = string sexp in
if String.is_prefix s ~prefix:"-" then
2017-02-24 10:49:27 +00:00
of_sexp_error sexp "flag not allowed here"
2016-12-02 13:54:32 +00:00
else
of_string s
let compare : t -> t -> int = Pervasives.compare
end
module Pp_or_flags = struct
2016-12-02 13:54:32 +00:00
type t =
| PP of Pp.t
| Flags of string list
2016-12-02 13:54:32 +00:00
let of_string s =
if String.is_prefix s ~prefix:"-" then
Flags [s]
2016-12-02 13:54:32 +00:00
else
PP (Pp.of_string s)
let t = function
| Atom (_, s) -> of_string s
| List (_, l) -> Flags (List.map l ~f:string)
2016-12-02 13:54:32 +00:00
let split l =
let pps, flags =
List.partition_map l ~f:(function
| PP pp -> Inl pp
| Flags s -> Inr s)
in
(pps, List.concat flags)
2016-12-02 13:54:32 +00:00
end
module Dep_conf = struct
type t =
| File of String_with_vars.t
| Alias of String_with_vars.t
| Glob_files of String_with_vars.t
| Files_recursively_in of String_with_vars.t
let t =
let t =
2017-02-26 21:28:30 +00:00
let cstr name f =
cstr name (String_with_vars.t @> nil) f
in
2016-12-02 13:54:32 +00:00
sum
2017-02-26 21:28:30 +00:00
[ cstr "file" (fun x -> File x)
; cstr "alias" (fun x -> Alias x)
; cstr "glob_files" (fun x -> Glob_files x)
; cstr "files_recursively_in" (fun x -> Files_recursively_in x)
2016-12-02 13:54:32 +00:00
]
in
fun sexp ->
match sexp with
| Atom _ -> File (String_with_vars.t sexp)
| List _ -> t sexp
2017-02-21 17:13:30 +00:00
open Sexp
let sexp_of_t = function
| File t ->
List [Atom "file" ; String_with_vars.sexp_of_t t]
| Alias t ->
List [Atom "alias" ; String_with_vars.sexp_of_t t]
| Glob_files t ->
List [Atom "glob_files" ; String_with_vars.sexp_of_t t]
| Files_recursively_in t ->
List [Atom "files_recursively_in" ; String_with_vars.sexp_of_t t]
2016-12-02 13:54:32 +00:00
end
module Preprocess = struct
2017-03-05 01:59:05 +00:00
type pps = { pps : Pp.t list; flags : string list }
2016-12-02 13:54:32 +00:00
type t =
| No_preprocessing
2017-03-06 14:34:53 +00:00
| Action of Action.Mini_shexp.Unexpanded.t
| Pps of pps
2016-12-02 13:54:32 +00:00
let t =
sum
2017-02-26 21:28:30 +00:00
[ cstr "no_preprocessing" nil No_preprocessing
2017-03-06 14:34:53 +00:00
; cstr "action" (Action.Mini_shexp.Unexpanded.t @> nil) (fun x -> Action x)
; cstr "pps" (list Pp_or_flags.t @> nil) (fun l ->
let pps, flags = Pp_or_flags.split l in
2017-03-05 01:59:05 +00:00
Pps { pps; flags })
2016-12-02 13:54:32 +00:00
]
2017-03-05 01:59:05 +00:00
let pps = function
2016-12-02 13:54:32 +00:00
| Pps { pps; _ } -> pps
2017-03-05 01:59:05 +00:00
| _ -> []
2016-12-02 13:54:32 +00:00
end
module Per_file = struct
type 'a t =
| For_all of 'a
| Per_file of 'a String_map.t
2016-12-02 13:54:32 +00:00
let t a sexp =
2016-12-02 13:54:32 +00:00
match sexp with
2017-02-25 17:53:39 +00:00
| List (_, Atom (_, "per_file") :: rest) -> begin
2016-12-02 13:54:32 +00:00
List.concat_map rest ~f:(fun sexp ->
let pp, names = pair a module_names sexp in
2016-12-02 13:54:32 +00:00
List.map (String_set.elements names) ~f:(fun name -> (name, pp)))
|> String_map.of_alist
|> function
| Ok map -> Per_file map
| Error (name, _, _) ->
2017-02-24 10:49:27 +00:00
of_sexp_error sexp (sprintf "module %s present in two different sets" name)
2016-12-02 13:54:32 +00:00
end
| sexp -> For_all (a sexp)
end
module Preprocess_map = struct
type t = Preprocess.t Per_file.t
let t = Per_file.t Preprocess.t
let find module_name (t : t) =
match t with
| For_all pp -> pp
| Per_file map -> String_map.find_default module_name map ~default:No_preprocessing
let default : t = For_all No_preprocessing
2016-12-02 13:54:32 +00:00
2017-03-05 01:59:05 +00:00
module Pp_set = Set.Make(Pp)
let pps : t -> _ = function
2017-03-05 01:59:05 +00:00
| For_all pp -> Preprocess.pps pp
2016-12-02 13:54:32 +00:00
| Per_file map ->
String_map.fold map ~init:Pp_set.empty ~f:(fun ~key:_ ~data:pp acc ->
2017-03-05 01:59:05 +00:00
Pp_set.union acc (Pp_set.of_list (Preprocess.pps pp)))
|> Pp_set.elements
2016-12-02 13:54:32 +00:00
end
module Lint = struct
type t = Pps of Preprocess.pps
let t =
sum
[ cstr "pps" (list Pp_or_flags.t @> nil) (fun l ->
let pps, flags = Pp_or_flags.split l in
2017-03-05 01:59:05 +00:00
Pps { pps; flags })
]
end
2016-12-02 13:54:32 +00:00
let field_osl name =
field name Ordered_set_lang.t ~default:Ordered_set_lang.standard
let field_oslu name =
field name Ordered_set_lang.Unexpanded.t ~default:Ordered_set_lang.Unexpanded.standard
2016-12-15 11:20:46 +00:00
module Js_of_ocaml = struct
2016-12-15 11:20:46 +00:00
type t =
{ flags : Ordered_set_lang.t
2016-12-15 11:20:46 +00:00
; javascript_files : string list
}
let t =
record
(field_osl "flags" >>= fun flags ->
field "javascript_files" (list string) ~default:[] >>= fun javascript_files ->
return { flags; javascript_files })
let default =
{ flags = Ordered_set_lang.standard
; javascript_files = [] }
2016-12-15 11:20:46 +00:00
end
2017-01-25 15:41:22 +00:00
module Lib_dep = struct
type choice =
{ required : String_set.t
; forbidden : String_set.t
; file : string
2017-01-25 15:41:22 +00:00
}
type select =
{ result_fn : string
; choices : choice list
; loc : Loc.t (* For error messages *)
}
2017-02-26 21:28:30 +00:00
2017-01-25 15:41:22 +00:00
type t =
| Direct of string
2017-02-26 21:28:30 +00:00
| Select of select
2017-01-25 15:41:22 +00:00
let choice = function
2017-02-25 17:53:39 +00:00
| List (_, l) as sexp ->
let rec loop required forbidden = function
| [Atom (_, "->"); fsexp] ->
let common = String_set.inter required forbidden in
if not (String_set.is_empty common) then
of_sexp_errorf sexp
"library %S is both required and forbidden in this clause"
(String_set.choose common);
{ required
; forbidden
; file = file fsexp
2017-01-26 10:53:37 +00:00
}
2017-02-25 17:53:39 +00:00
| Atom (_, "->") :: _ | List _ :: _ | [] ->
2017-02-24 10:49:27 +00:00
of_sexp_error sexp "(<[!]libraries>... -> <file>) expected"
2017-02-25 17:53:39 +00:00
| Atom (_, s) :: l ->
2017-01-26 10:53:37 +00:00
let len = String.length s in
if len > 0 && s.[0] = '!' then
let s = String.sub s ~pos:1 ~len:(len - 1) in
loop required (String_set.add s forbidden) l
2017-01-26 10:53:37 +00:00
else
loop (String_set.add s required) forbidden l
2017-01-26 10:53:37 +00:00
in
loop String_set.empty String_set.empty l
2017-02-24 10:49:27 +00:00
| sexp -> of_sexp_error sexp "(<library-name> <code>) expected"
2017-01-25 15:41:22 +00:00
let t = function
2017-02-25 17:53:39 +00:00
| Atom (_, s) ->
2017-01-25 15:41:22 +00:00
Direct s
| List (loc, Atom (_, "select") :: m :: Atom (_, "from") :: libs) ->
2017-01-25 15:41:22 +00:00
Select { result_fn = file m
; choices = List.map libs ~f:choice
; loc
2017-01-25 15:41:22 +00:00
}
| sexp ->
2017-02-24 10:49:27 +00:00
of_sexp_error sexp "<library> or (select <module> from <libraries...>) expected"
2017-01-25 15:41:22 +00:00
let to_lib_names = function
| Direct s -> [s]
2017-01-26 10:53:37 +00:00
| Select s ->
List.fold_left s.choices ~init:String_set.empty ~f:(fun acc x ->
String_set.union acc (String_set.union x.required x.forbidden))
|> String_set.elements
2017-01-25 15:41:22 +00:00
let direct s = Direct s
end
module Lib_deps = struct
type t = Lib_dep.t list
type kind =
| Required
| Optional
| Forbidden
let t sexp =
let t = list Lib_dep.t sexp in
let add kind name acc =
match String_map.find name acc with
| None -> String_map.add acc ~key:name ~data:kind
| Some kind' ->
match kind, kind' with
| Required, Required ->
of_sexp_errorf sexp "library %S is present twice" name
| (Optional|Forbidden), (Optional|Forbidden) ->
acc
| Optional, Required | Required, Optional ->
of_sexp_errorf sexp
"library %S is present both as an optional and required dependency"
name
| Forbidden, Required | Required, Forbidden ->
of_sexp_errorf sexp
"library %S is present both as a forbidden and required dependency"
name
in
ignore (
List.fold_left t ~init:String_map.empty ~f:(fun acc x ->
match x with
| Lib_dep.Direct s -> add Required s acc
| Select { choices; _ } ->
List.fold_left choices ~init:acc ~f:(fun acc c ->
let acc = String_set.fold c.Lib_dep.required ~init:acc ~f:(add Optional) in
String_set.fold c.forbidden ~init:acc ~f:(add Forbidden)))
: kind String_map.t);
t
end
2017-02-23 18:46:12 +00:00
module Buildable = struct
type t =
{ modules : Ordered_set_lang.t
; libraries : Lib_dep.t list
; preprocess : Preprocess_map.t
; preprocessor_deps : Dep_conf.t list
; lint : Lint.t Per_file.t option
2017-02-23 18:46:12 +00:00
; flags : Ordered_set_lang.t
; ocamlc_flags : Ordered_set_lang.t
; ocamlopt_flags : Ordered_set_lang.t
2017-05-02 12:30:58 +00:00
; js_of_ocaml : Js_of_ocaml.t
2017-02-23 18:46:12 +00:00
}
let v1 =
field "preprocess" Preprocess_map.t ~default:Preprocess_map.default
2017-02-23 18:46:12 +00:00
>>= fun preprocess ->
field "preprocessor_deps" (list Dep_conf.t) ~default:[]
>>= fun preprocessor_deps ->
field_o "lint" (Per_file.t Lint.t)
>>= fun lint ->
2017-02-23 18:46:12 +00:00
field "modules" (fun s -> Ordered_set_lang.(map (t s)) ~f:String.capitalize_ascii)
~default:Ordered_set_lang.standard
>>= fun modules ->
field "libraries" Lib_deps.t ~default:[]
2017-02-23 18:46:12 +00:00
>>= fun libraries ->
field_osl "flags" >>= fun flags ->
field_osl "ocamlc_flags" >>= fun ocamlc_flags ->
field_osl "ocamlopt_flags" >>= fun ocamlopt_flags ->
2017-05-02 12:30:58 +00:00
field "js_of_ocaml" (Js_of_ocaml.t) ~default:Js_of_ocaml.default >>= fun js_of_ocaml ->
2017-02-23 18:46:12 +00:00
return
{ preprocess
; preprocessor_deps
; lint
2017-02-23 18:46:12 +00:00
; modules
; libraries
; flags
; ocamlc_flags
; ocamlopt_flags
2017-05-02 12:30:58 +00:00
; js_of_ocaml
2017-02-23 18:46:12 +00:00
}
2017-02-24 09:41:32 +00:00
2017-03-05 13:16:25 +00:00
let single_preprocess t =
match t.preprocess with
| For_all pp -> pp
| Per_file _ -> No_preprocessing
2017-02-23 18:46:12 +00:00
end
module Public_lib = struct
type t =
{ name : string (* Full public name *)
; package : string (* Package it is part of *)
; sub_dir : string option (* Subdirectory inside the installation directory *)
}
let of_public_name s =
match String.split s ~on:'.' with
| [] -> assert false
| pkg :: rest ->
{ package = pkg
; sub_dir = if rest = [] then None else Some (String.concat rest ~sep:"/")
; name = s
}
let t sexp = of_public_name (string sexp)
end
2016-12-02 13:54:32 +00:00
module Library = struct
2016-12-15 11:20:46 +00:00
module Kind = struct
type t =
| Normal
2017-03-05 11:57:27 +00:00
| Ppx_deriver
2016-12-15 11:20:46 +00:00
| Ppx_rewriter
let t =
2017-02-26 21:28:30 +00:00
enum
2017-03-05 11:57:27 +00:00
[ "normal" , Normal
; "ppx_deriver" , Ppx_deriver
; "ppx_rewriter" , Ppx_rewriter
2016-12-15 11:20:46 +00:00
]
end
2016-12-02 13:54:32 +00:00
type t =
2016-12-16 10:58:59 +00:00
{ name : string
; public : Public_lib.t option
2016-12-16 10:58:59 +00:00
; synopsis : string option
2017-01-19 13:19:13 +00:00
; install_c_headers : string list
2016-12-16 10:58:59 +00:00
; ppx_runtime_libraries : string list
; modes : Mode.t list
; kind : Kind.t
; c_flags : Ordered_set_lang.Unexpanded.t
; c_names : string list
; cxx_flags : Ordered_set_lang.Unexpanded.t
; cxx_names : string list
; includes : String_with_vars.t list
; library_flags : String_with_vars.t list
2017-01-06 17:17:38 +00:00
; c_library_flags : Ordered_set_lang.Unexpanded.t
2016-12-15 11:20:46 +00:00
; self_build_stubs_archive : string option
2016-12-16 10:58:59 +00:00
; virtual_deps : string list
; wrapped : bool
; optional : bool
2017-02-23 18:46:12 +00:00
; buildable : Buildable.t
2017-04-05 15:14:06 +00:00
; dynlink : bool
2016-12-02 13:54:32 +00:00
}
2017-02-24 09:41:32 +00:00
let v1 =
record
(Buildable.v1 >>= fun buildable ->
field "name" library_name >>= fun name ->
field_o "public_name" Public_lib.t >>= fun public ->
field_o "synopsis" string >>= fun synopsis ->
field "install_c_headers" (list string) ~default:[] >>= fun install_c_headers ->
field "ppx_runtime_libraries" (list string) ~default:[] >>= fun ppx_runtime_libraries ->
field_oslu "c_flags" >>= fun c_flags ->
field_oslu "cxx_flags" >>= fun cxx_flags ->
field "c_names" (list string) ~default:[] >>= fun c_names ->
field "cxx_names" (list string) ~default:[] >>= fun cxx_names ->
field "library_flags" (list String_with_vars.t) ~default:[] >>= fun library_flags ->
field_oslu "c_library_flags" >>= fun c_library_flags ->
field "virtual_deps" (list string) ~default:[] >>= fun virtual_deps ->
field "modes" (list Mode.t) ~default:Mode.all >>= fun modes ->
field "kind" Kind.t ~default:Kind.Normal >>= fun kind ->
field "wrapped" bool ~default:true >>= fun wrapped ->
field_b "optional" >>= fun optional ->
field "self_build_stubs_archive" (option string) ~default:None >>= fun self_build_stubs_archive ->
field_b "no_dynlink" >>= fun no_dynlink ->
2017-02-24 09:41:32 +00:00
return
{ name
; public
2017-02-24 09:41:32 +00:00
; synopsis
; install_c_headers
; ppx_runtime_libraries
; modes
; kind
; c_names
; c_flags
; cxx_names
; cxx_flags
; includes = []
; library_flags
; c_library_flags
2017-03-07 13:21:54 +00:00
; self_build_stubs_archive
2017-02-24 09:41:32 +00:00
; virtual_deps
; wrapped
; optional
; buildable
2017-04-05 15:14:06 +00:00
; dynlink = not no_dynlink
2017-02-24 09:41:32 +00:00
})
2016-12-15 11:20:46 +00:00
let has_stubs t =
match t.c_names, t.cxx_names, t.self_build_stubs_archive with
| [], [], None -> false
| _ -> true
2016-12-31 13:26:29 +00:00
let stubs_archive t ~dir ~ext_lib =
Path.relative dir (sprintf "lib%s_stubs%s" t.name ext_lib)
2017-01-25 15:41:22 +00:00
let all_lib_deps t =
2017-02-23 18:46:12 +00:00
List.map t.virtual_deps ~f:(fun s -> Lib_dep.Direct s) @ t.buildable.libraries
2016-12-02 13:54:32 +00:00
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, <name> or (<name> as <install-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
})
end
2016-12-02 13:54:32 +00:00
module Executables = struct
type t =
{ names : string list
; link_executables : bool
; link_flags : string list
; buildable : Buildable.t
2016-12-02 13:54:32 +00:00
}
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 =
2017-02-24 09:41:32 +00:00
record
(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])
2016-12-02 13:54:32 +00:00
end
module Rule = struct
type t =
{ targets : string list (** List of files in the current directory *)
; deps : Dep_conf.t list
; action : Action.Mini_shexp.Unexpanded.t
2016-12-02 13:54:32 +00:00
}
let v1 =
record
(field "targets" (list file_in_current_dir) >>= fun targets ->
field "deps" (list Dep_conf.t) ~default:[] >>= fun deps ->
field "action" Action.Mini_shexp.Unexpanded.t >>= fun action ->
return { targets; deps; action })
2016-12-02 13:54:32 +00:00
let ocamllex_v1 names =
let str s = String_with_vars.of_string s in
List.map names ~f:(fun name ->
let src = name ^ ".mll" in
let dst = name ^ ".ml" in
{ targets = [dst]
; deps = [File (str src)]
; action =
Chdir
(str "${ROOT}",
Run (str "${bin:ocamllex}",
[str "-q"; str "-o"; str "${@}"; str "${<}"]))
})
let ocamlyacc_v1 names =
let str s = String_with_vars.of_string s in
List.map names ~f:(fun name ->
2017-03-01 15:18:09 +00:00
let src = name ^ ".mly" in
{ targets = [name ^ ".ml"; name ^ ".mli"]
; deps = [File (str src)]
; action =
Chdir
(str "${ROOT}",
Run (str "${bin:ocamlyacc}",
[str "${<}"]))
})
2016-12-02 13:54:32 +00:00
end
module Menhir = struct
type t =
{ base : string option
; flags : String_with_vars.t list
; modules: string list
}
let v1 =
record
(field_o "merge_into" string >>= fun base ->
field "flags" (list String_with_vars.t) ~default:[] >>= fun flags ->
field "modules" (list string) >>= fun modules ->
return
{ base
; flags
; modules
}
)
let v1_to_rule t =
let str s = String_with_vars.of_string s in
let targets n = [n ^ ".ml"; n ^ ".mli"] in
match t.base with
| None ->
List.map t.modules ~f:(fun name ->
let src = name ^ ".mly" in
{ Rule.
targets = targets name
; deps = [Dep_conf.File (str src)]
; action =
Chdir
(str "${ROOT}",
Run (str "${bin:menhir}",
t.flags @ [str "${<}"]))
})
| Some base ->
let mly m = str (m ^ ".mly") in
[{ Rule.
targets = targets base
; deps = List.map ~f:(fun m -> Dep_conf.File (mly m)) t.modules
; action =
Chdir
(str "${ROOT}",
Run (str "${bin:menhir}",
[ str "--base"
; str base
]
@ t.flags
@ (List.map ~f:mly t.modules))
)
}]
end
2016-12-02 13:54:32 +00:00
module Provides = struct
type t =
{ name : string
; file : string
}
2017-02-24 09:41:32 +00:00
let v1 sexp =
2016-12-02 13:54:32 +00:00
match sexp with
2017-02-25 17:53:39 +00:00
| Atom (_, s) ->
2016-12-02 13:54:32 +00:00
{ name = s
; file =
match String.lsplit2 s ~on:':' with
| None -> s
| Some (_, s) -> s
}
2017-02-25 17:53:39 +00:00
| List (_, [Atom (_, s); List (_, [Atom (_, "file"); Atom (_, file)])]) ->
2016-12-02 13:54:32 +00:00
{ name = s
; file
}
| sexp ->
2017-02-24 10:49:27 +00:00
of_sexp_error sexp "[<name>] or [<name> (file <file>)] expected"
2016-12-02 13:54:32 +00:00
end
2017-02-21 17:13:30 +00:00
module Alias_conf = struct
type t =
{ name : string
; deps : Dep_conf.t list
; action : Action.Mini_shexp.Unexpanded.t option
; package : string option
2017-02-21 17:13:30 +00:00
}
let v1 =
record
(field "name" string >>= fun name ->
field "deps" (list Dep_conf.t) ~default:[] >>= fun deps ->
field_o "package" string >>= fun package ->
field_o "action" Action.Mini_shexp.Unexpanded.t >>= fun action ->
return
{ name
; deps
; action
; package
})
2017-02-21 17:13:30 +00:00
end
2016-12-02 13:54:32 +00:00
module Stanza = struct
type t =
| Library of Library.t
| Executables of Executables.t
| Rule of Rule.t
| Provides of Provides.t
2016-12-15 11:20:46 +00:00
| Install of Install_conf.t
2017-02-21 17:13:30 +00:00
| Alias of Alias_conf.t
let rules l = List.map l ~f:(fun x -> Rule x)
2016-12-02 13:54:32 +00:00
let execs (exe, install) =
match install with
| None -> [Executables exe]
| Some i -> [Executables exe; Install i]
2017-02-24 09:41:32 +00:00
let v1 =
sum
[ cstr "library" (Library.v1 @> nil) (fun x -> [Library 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))
; cstr "menhir" (Menhir.v1 @> nil) (fun x -> rules (Menhir.v1_to_rule x))
; cstr "install" (Install_conf.v1 @> nil) (fun x -> [Install x])
; cstr "alias" (Alias_conf.v1 @> nil) (fun x -> [Alias x])
(* Just for validation and error messages *)
; cstr "jbuild_version" (Jbuild_version.t @> nil) (fun _ -> [])
2017-02-24 09:41:32 +00:00
]
let select : Jbuild_version.t -> t list Sexp.Of_sexp.t = function
| V1 -> v1
| Vjs -> v1
2016-12-02 13:54:32 +00:00
let lib_names ts =
List.fold_left ts ~init:String_set.empty ~f:(fun acc (_, stanzas) ->
List.fold_left stanzas ~init:acc ~f:(fun acc -> function
| Library lib ->
String_set.add lib.name
(match lib.public with
2016-12-02 13:54:32 +00:00
| None -> acc
| Some { name; _ } -> String_set.add name acc)
2016-12-02 13:54:32 +00:00
| _ -> acc))
end
module Stanzas = struct
type t = Stanza.t list
let resolve_packages ts ~dir
~(visible_packages : Package.t String_map.t)
~(closest_packages : Package.t list) =
let error fmt =
2017-02-24 11:16:55 +00:00
Loc.fail (Loc.in_file (Path.to_string (Path.relative dir "jbuild"))) fmt
in
let package_listing packages =
let longest_pkg = List.longest_map packages ~f:(fun p -> p.Package.name) in
String.concat ~sep:"\n"
(List.map packages ~f:(fun pkg ->
2017-02-24 18:21:22 +00:00
sprintf "- %-*s (because of %s)" longest_pkg pkg.Package.name
(Path.to_string (Path.relative pkg.path (pkg.name ^ ".opam")))))
in
let check pkg =
if not (String_map.mem pkg visible_packages) then (
if String_map.is_empty visible_packages then
error "package %S is not visible here.\n\
In fact I know of no packages here, \
in order for me to know that package\n\
%S exist, you need to add a %S file at the root of your project."
pkg pkg (pkg ^ ".opam")
else
error "package %S is not visible here.\n\
The only packages I know of in %S are:\n\
%s%s"
pkg
(Path.to_string dir)
(package_listing (String_map.values visible_packages))
(hint pkg (String_map.keys visible_packages))
)
in
let default () =
match closest_packages with
| [pkg] -> pkg
| [] ->
error "no packages are defined here.\n\
What do you want me to do with this (install ...) stanzas?.\n\
You need to add a <package>.opam file at the root \
of your project so that\n\
I know that you want to install things as part of pacakge <package>."
| _ :: _ :: _ ->
error "I can't determine automatically which package your (install ...) \
stanzas are for in this directory. I have the choice between these ones:\n\
%s\n\
You need to add a (package ...) field in your (install ...) stanzas"
(package_listing closest_packages)
in
List.map ts ~f:(fun (stanza : Stanza.t) ->
match stanza with
| Library { public = Some { package; _ }; _ } ->
check package;
stanza
| Install { package = Some pkg; _ } ->
check pkg;
stanza
| Install ({ package = None; _ } as install) ->
Install { install with package = Some (default ()).name }
| _ -> stanza)
2017-02-26 19:49:54 +00:00
let parse sexps ~dir ~visible_packages =
let versions, sexps =
List.partition_map sexps ~f:(function
2017-02-27 11:58:06 +00:00
| List (loc, [Atom (_, "jbuild_version"); ver]) ->
2017-02-26 19:49:54 +00:00
Inl (Jbuild_version.t ver, loc)
| sexp -> Inr sexp)
in
let version =
match versions with
| [] -> Jbuild_version.latest_stable
| [(v, _)] -> v
| _ :: (_, loc) :: _ ->
2017-02-27 11:58:06 +00:00
Loc.fail loc "jbuild_version specified too many times"
2017-02-26 19:49:54 +00:00
in
List.concat_map sexps ~f:(Stanza.select version)
|> resolve_packages ~dir ~visible_packages
2016-12-02 13:54:32 +00:00
end